Комбинируйте возможности классов (миксинов) с неоднозначными вызовами

Я читал о технике "микширования" в C++, но есть кое-что, чего я не понимаю, и, похоже, это ограничение языка, которое не позволяет делать это в целом из-за неоднозначностей, которые компилятор (и стандарт отказываются разрешать, даже если можно).

Идея миксина состоит в том, чтобы объединить возможности из разных частей. Иногда возможности можно было назвать одним и тем же именем, потому что они обычно делают одно и то же.

struct A{
    void f(int){}
};

struct B{
    void f(double){}
};

Я мог бы объединить услуги двух классов с

struct CombinedAB : A, B{};

Однако, когда я использую его, я получаю ошибку компиляции: "запрос на член 'f' неоднозначен" (ссылка Godbolt).

CombinedAB c; c.f(3.14);  // 3.14 is a double, I expect B::f to be called.

Итак, компилятор знает, что fсуществует, но отказывается решить, какой из них следует вызывать. Это генерирует эту ошибку:

main.cpp: In function ‘int main()’:
main.cpp:23:21: error: request for member ‘f’ is ambiguous
     CombinedAB c; c.f(3.14);
                     ^
main.cpp:16:10: note: candidates are: void B::f(double)
     void f(double){}
          ^
main.cpp:12:10: note:                 void A::f(int)
     void f(int){}
          ^

Конечно, я мог бы изменить класс и использовать эту идиому ", чтобы получить полноценную версию f перегрузки ".

struct CombineAB : A, B{
    using A::f;
    using B::f;
};

И это работает, но проблема в том, что CombineABне может быть сделано в целом. Ближайшим из них будет:

template<class T1, class T2>
struct combination : T1, T2{
 // using T1::all_provided_by_T1;
 // using T2::all_provided_by_T2;
};

Но я не могу не добавить эквивалент using T1::f; using T2::f, прежде всего потому, что combination необходимо знать обо всех возможных функциях в T1, T2.

Таким образом, кажется, что комбинация должна определяться в каждом конкретном случае. Например с

template<class T1, class T2>
struct combination_of_f_service : T1, T2{
  using T1::f; using T2::f;
}

Это противоречит цели, потому что, если T1 и T2 предоставляют 20 функций с одинаковыми именами (как в моем случае), классу необходимо будет повторить все имена этих функций. Хуже того, это невозможно сделать с помощью дополнительных аргументов шаблона (не так ли?).

Это правильный способ комбинировать классы в C++? Есть ли обходной путь или это просто слишком наивный взгляд на проблему? Я готов принять вариант, включающий довольно много кода шаблона, если это необходимо.


Проблема сохраняется, даже если создание экземпляра не является частью уравнения и fэто статическая функция. Похоже, что язык серьезно относится к изменению или объединению значений функций-членов в базовом классе.

1 ответ

Частичный ответ на мой вопрос:

Собственно говоря, я понимаю, что это особенность "точечной" нотации и насколько непреклонен C++ с этим синтаксисом. Это не кажется фундаментальным ограничением, это блок, который был специально введен в язык, чтобы ограничить использование точечной нотации. Возможно, это историческая причуда, связанная с возможным неправильным использованием виртуальных функций.

Как только я отпущу "точечную" нотацию, поведение миксина внезапно становится возможным с эквивалентным (но несколько неожиданным) синтаксисом.

struct A{
    void f(int){}
    friend void f(A& self, int i){self.f(i);}
};

struct B{
    void f(double){}
    friend void f(B& self, double d){self.f(d);}
};

struct C : A, B{
//    using A::f;
//    using B::f;
};

template<class T1, class T2>
struct combination : T1, T2{};

int main(){
    C c;
//    c.f(5.1); // error, ambiguous call
    f(c, 5.1);

    combination<A, B> c2;
    f(c2, 5.1);
}

Интересно также отметить, что если это предложение будет реализовано https://isocpp.org/blog/2016/02/a-bit-of-background-for-the-unified-call-proposal, эту проблему необходимо будет устранить. решено как-то. Это первый случай, когда я вижу, что принудительное использование обозначений c.f(a)является принципиально более жесткие ограничения, что вынудив f(c, a)обозначение. Я пришел к выводу, что миксины не работают через точечную нотацию. Жаль, что это также влияет на статические функции-члены (обозначение "::").

Я все еще был бы заинтересован в некоторой технике использования "точечной" записи, если кто-нибудь знает.

Другие вопросы по тегам