Почему множественные наследуемые функции с одним и тем же именем, но разными сигнатурами не рассматриваются как перегруженные функции?
Следующий фрагмент кода выдает ошибку "неоднозначный вызов 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;}