Видимость перегрузок функций, разделенных между базовым и классами Derved

0

Я пытаюсь реорганизовать некоторый код шаблона посетителя, чтобы удалить дублирование кода. Суть этой задачи требует разбиения перегрузок функций из существующего 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)
Теги:
inheritance
visitor

1 ответ

2
Лучший ответ

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

Компилятор в основном перемещается назад по областям, чтобы найти область, где есть хотя бы один элемент с именем, которое он ищет. Затем он смотрит на все с таким именем в этой области и делает перегрузочное разрешение на них. Если оттуда что-то есть во внешнем пространстве (например, родительский класс) с таким же именем, оно не будет включено в это разрешение перегрузки.

Однако вы можете привести унаследованное имя в область видимости:

struct base {
    void foo(int);
};

class derived : public base {
    using base::foo;

    void foo();
};

Теперь, если вы вызываете foo в производном классе, foo из производного класса и базового класса рассматриваются как набор перегрузки, поэтому правильный вызов будет основан на аргументе, который вы передаете (или его отсутствии).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню