Почему многонаследованные функции с таким же именем, но с разными сигнатурами не рассматриваются как перегруженные функции?
Следующий фрагмент создает ошибку "ambigious call to 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;
}
Ответы
Ответ 1
Правила поиска членов определены в Разделе 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)
находится внутри области C. На самом деле d.foo(5);
вызывает void foo(float)
, а не версию int
.
Ответ 2
Будет ли это работать на вас?
struct Derived : public Base1, public Base2{
using Base2::foo;}
Ответ 3
Поиск имени - это отдельная фаза для перегрузки разрешения.
Сначала начинается поиск имени. Это процесс определения того, к какой области относится это имя. В этом случае мы должны решить, означает ли 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)
- два идентификатора для одной функции; но в целом для процесса поиска имен и обработки перегрузки не имеет значения, являются ли они двумя отдельными функциями или нет.