Я пытаюсь реорганизовать некоторый код шаблона посетителя, чтобы удалить дублирование кода. Суть этой задачи требует разбиения перегрузок функций из существующего API на два: некоторые входят в базовый класс, в то время как другие переходят в производный класс, который расширяет эту базу.
При попытке разделить API между базовым и производным классами я попал в неожиданную ошибку компиляции. Пытаясь отвлечь внимание от этой истории, я распутывал/перегонял мою проблему в следующем примере (C++ 11):
#include <iostream>
class X
{
public:
virtual const char * name() const =0;
};
class Y : public X
{
public:
virtual const char * name() const override { return "Y"; }
};
class Z : public X
{
public:
virtual const char * name() const override { return "Z"; }
};
class APIBase // The API is split between this base class...
{
public:
virtual void foo(Y & y) =0;
};
class APIDerived : public APIBase // ..and this derived class
{
public:
virtual void foo(Z & z) =0;
};
class APIImplementation : public APIDerived
{
public:
virtual void foo(Y & y) override {
std::cout << "foo(" << y.name() << ")" << std::endl;
}
virtual void foo(Z & z) override {
std::cout << "foo(" << z.name() << ")" << std::endl;
}
};
class A
{
public:
APIDerived & api() { return m_api; }
private:
APIImplementation m_api;
};
int main(int argc, char * argv[])
{
Y y;
Z z;
A a;
a.api().foo(y);
a.api().foo(z);
return 0;
}
Основная идея заключается в том, что класс A обеспечивает реализацию API, определенного в APIBase и APID, с помощью вызова api(), который затем может действовать на объект, полученный из класса X, и делать что-то другое в зависимости от того, является ли это Y или Z.
Я ожидаю, что этот код даст мне следующий результат при запуске:
foo(Y)
foo(Z)
Однако при компиляции этого кода gcc дает мне следующую ошибку:
intf.cpp: In function ‘int main(int, char**):
intf.cpp:57:16: error: no matching function for call to ‘APIDerived::foo(Y&)
a.api().foo(y);
^
intf.cpp:57:16: note: candidate is:
intf.cpp:30:16: note: virtual void APIDerived::foo(Z&)
virtual void foo(Z & z) =0;
^
intf.cpp:30:16: note: no known conversion for argument 1 from ‘Y to ‘Z&
Есть два способа сделать этот примерный код компиляцией и дать ожидаемый результат, но я не уверен, что отличает их от оригинала в глазах компилятора (или C++).
1. Воссоедините API, поместив обе декларации чистой функции foo() в APIBase или APIDerived (но не разделенные между ними), например:
class APIBase
{
public:
virtual void foo(Y & y) =0;
virtual void foo(Z & z) =0;
};
2. Измените класс A так, чтобы он был получен из APII-реализации и вырезал вызовы перенаправления api(), например:
class A : public APIImplementation {};
int main(int argc, char * argv[])
{
Y y;
Z z;
A a;
a.foo(y);
a.foo(z);
return 0;
}
Я не хочу, чтобы это было так. Я также не хочу убрать наследование в пользу шаблонов.
Я новичок в C++: пожалуйста, помогите мне понять, почему этот код кода не компилируется и, если возможно, предлагает обходные пути, которые не потребуют от меня выполнения шагов (1) или (2) выше?
Технические подробности:
Platform: Centos 7, Linux 3.10.0-123.6.3.el7.x86_64
Compiler: gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-16)
По умолчанию f
в базовом классе и f
в производном классе не рассматриваются как перегрузки, и перегрузка между ними не выполняется.
Компилятор в основном перемещается назад по областям, чтобы найти область, где есть хотя бы один элемент с именем, которое он ищет. Затем он смотрит на все с таким именем в этой области и делает перегрузочное разрешение на них. Если оттуда что-то есть во внешнем пространстве (например, родительский класс) с таким же именем, оно не будет включено в это разрешение перегрузки.
Однако вы можете привести унаследованное имя в область видимости:
struct base {
void foo(int);
};
class derived : public base {
using base::foo;
void foo();
};
Теперь, если вы вызываете foo
в производном классе, foo
из производного класса и базового класса рассматриваются как набор перегрузки, поэтому правильный вызов будет основан на аргументе, который вы передаете (или его отсутствии).