Почему множественные наследуемые функции с одним и тем же именем, но разными сигнатурами не рассматриваются как перегруженные функции?

Следующий фрагмент кода выдает ошибку "неоднозначный вызов foo" во время компиляции, и я хотел бы знать, есть ли способ обойти эту проблему без полной квалификации вызова foo:

#include <iostream>

struct Base1{
    void foo(int){
    }
};

struct Base2{
    void foo(float){
    }
};

struct Derived : public Base1, public Base2{
};

int main(){
    Derived d;
    d.foo(5);

    std::cin.get();
    return 0;
}

Итак, вопрос, как следует из названия. Идеи? Я имею в виду следующее работает без нареканий:

#include <iostream>

struct Base{
    void foo(int){
    }
};

struct Derived : public Base{
    void foo(float){
    }
};

int main(){
    Derived d;
    d.foo(5);

    std::cin.get();
    return 0;
}

3 ответа

Решение

Правила поиска членов определены в Разделе 10.2/2.

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

class A {
public:
  int f(int);

};
class B {
public:
   int f();

};
class C : public A, public B {};
int main()
{
     C c;
     c.f(); // ambiguous
}

Таким образом, вы можете использовать using декларации A::f а также B::f чтобы решить эту двусмысленность

class C : public A, public B {
     using A::f;
     using B::f;

};

int main()
{
     C c;
     c.f(); // fine
}

Второй код работает без нареканий, потому что void foo(float) находится в области видимости С. На самом деле d.foo(5); звонки void foo(float) а не int версия.

Поиск имени - это отдельная фаза для разрешения перегрузки.

Поиск имени происходит первым. Это процесс определения того, к какой области относится имя. В этом случае мы должны решить, стоит ли d.foo средства d.D::foo, или же d.B1::foo, или же d.B2::foo, Правила поиска имени не принимают во внимание параметры функции или что-либо еще; это чисто об именах и сферах.

Только после того, как это решение было принято, мы затем выполняем разрешение перегрузки для различных перегрузок функции в области, в которой было найдено имя.

В вашем примере звоните d.foo() найдет D::foo() если бы была такая функция. Но нет ни одного. Таким образом, работая в обратном направлении, он пробует базовые классы. Сейчас foo мог бы одинаково смотреть до B1::foo или же B2::foo так что это неоднозначно.

По той же причине вы получите двусмысленность, назвав неквалифицированную foo(5); внутри D функция-член.


Эффект от рекомендуемого решения:

struct Derived : public Base1, public Base2{
    using Base1::foo;
    using Base2::foo;

в том, что это создает имя D::fooи заставляет его идентифицировать две функции. Результатом является то, что d.foo решает в d.D::fooи затем разрешение перегрузки может произойти для этих двух функций, которые идентифицируются D::foo,

Примечание: в этом примере D::foo(int) а также Base1::foo(int) два идентификатора для одной функции; но в целом, для процесса поиска имен и разрешения перегрузки не имеет значения, являются ли они двумя отдельными функциями или нет.

Будет ли это работать для вас?

struct Derived : public Base1, public Base2{
   using Base2::foo;}
Другие вопросы по тегам