Неоднозначно, когда два суперкласса имеют функцию-член с одинаковым именем, но разными сигнатурами. - PullRequest
27 голосов
/ 02 апреля 2012
struct A {
    void f(int x) {}
};

struct B {
    template<typename T> void f(T x) {}
};

struct C : public A, public B {};

struct D {
    void f(int x){}
    template<typename T> void f(T x) {} 
};


int main(int argc, char **argv) {
    C c;
    c.f<int>(3);
    D d;
    d.f<int>(3);
}

По какой причине звонить d.f можно, но c.f дает

error: request for member ‘f’ is ambiguous
error: candidates are: template<class T> void B::f(T)
error:                 void A::f(int)

Ответы [ 5 ]

11 голосов
/ 02 апреля 2012

Первая часть связана с поиском имени члена, поэтому она не работает.

Я бы сослался на: 10.2/2 Member name lookup

Следующие шаги определяют результат поиска имени в области видимости класса C. Сначала каждое объявление для имени в классеи в каждом из его базовых классов подобъектов рассматривается.Имя элемента f в одном подобъекте B скрывает имя элемента f в подобъекте A, если A является подобъектом базового класса объекта B. Любые объявления, которые являются такими скрытыми, исключаются из рассмотрения.Каждое из этих объявлений, которое было введено с помощью объявления об использовании, считается от каждого подобъекта C, который имеет тип, содержащий объявление, обозначенное с помощью объявления об использовании.

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

Теперь, для вопроса с шаблонными функциями.

Согласно 13.3.1/7 Candidate functions and argument list

В каждомВ случае, когда кандидат является шаблоном функции, специализации кандидата на шаблон функции генерируются с использованием вывода аргумента шаблона (14.8.3, 14.8.2).Затем эти кандидаты обрабатываются как функции-кандидаты обычным способом.Заданное имя может ссылаться на один или несколько шаблонов функций, а также на набор перегруженных не шаблонных функций.В таком случае функции-кандидаты, сгенерированные из каждого шаблона функции, объединяются с набором не шаблонных функций-кандидатов.

И если вы продолжите читать, 13.3.3/1 Best viable function

F1 будетсчитается функцией лучше , если:

F1 - не шаблонная функция, а F2 - специализация шаблона функции

. ПоэтомуСледующий фрагмент кода компилируется и запускает функцию без шаблона:

D c;
c.f(1);
1 голос
/ 02 апреля 2012

Я считаю, что компилятор предпочитает A::f (не шаблонная функция), чем B::f без причины.
Кажется, это ошибка реализации компилятора больше, чем детали, зависящие от реализации.

Если вы добавите следующую строку, то компиляция пройдет нормально и выбрана правильная функция B::f<>:

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

[Забавно, что до тех пор, пока ::f не попадут в область действия C, они будут рассматриваться как инопланетные функции.]

0 голосов
/ 02 апреля 2012

Вероятно, происходит то, что создание экземпляра шаблона происходит отдельно для классов A и B, что заканчивается двумя void f(int) функциями.

Этого не происходит в D, поскольку там компилятор знает о функции void f(int) как о специализации и, следовательно, не специализирует T для int.

0 голосов
/ 02 апреля 2012

Рассмотрим этот более простой пример:

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

struct B{
 void f(float t){}
};


struct C:public A,public B{
};

struct D{
 void f(float n){}
 void f(int n){}
};


int main(){
 C c;
 c.f(3);

 D d;
 d.f(3);
}

В этом примере, как и у вас, D компилируется, а C - нет.
Если класс является производным, механизм поиска членов ведет себя по-разному.Он проверяет каждый базовый класс и объединяет их: в случае C;Каждый базовый класс соответствует поиску (A :: f (int) и B :: f (float)).При объединении их C решает, что они неоднозначны.

Для класса дела D: int выбрана версия вместо float, поскольку параметр является целым числом.

0 голосов
/ 02 апреля 2012

Компилятор не знает, какой метод вызывать из класса C, потому что шаблонный метод будет преобразован в void f (int) в случае типа int, поэтому у вас есть два метода с одним и тем же именем и одинаковыми аргументами, но членами разныхродительские классы.

template<typename T> void f(T x) {} 

или

void f(int)

попробуйте это:

c.B::f<int>(3);

или это для класса A:

c.A::f(3);
...