запрос члена `' неоднозначен в g++

Я получаю следующую ошибку компиляции в одном из моих классов, используя gcc 3.4.5 (mingw):

src/ModelTester/CModelTesterGui.cpp:1308: error: request for member `addListener' is ambiguous
include/utility/ISource.h:26: error: candidates are: void utility::ISource<T>::addListener(utility::IListener<T>*) [with T = const SConsolePacket&]
include/utility/ISource.h:26: error:                 void utility::ISource<T>::addListener(utility::IListener<T>*) [with T = const SControlPacket&]

Надеюсь, вы видите, что ISource<T> — это интерфейс шаблона, который просто указывает, что объект может быть информатором для объекта, который относится к некоторому подходящему типу IListener<T>. Так что меня раздражала идея о том, что по какой-то причине функции неоднозначны, хотя, насколько я могу судить, это не так. Метод addListener() перегружен для разных типов ввода IListener<const SConsolePacket&> и IListener<const SControlPacket&>. Использование:

m_controller->addListener( m_model );

Где m_model — указатель на объект IRigidBody, а IRigidBody наследуется только от IListener< const SControlPacket& > и определенно не от IListener< const SConsolePacket& >

В качестве проверки работоспособности я использовал doxygen для создания диаграммы иерархии классов, и doxygen согласен со мной, что IRigidBody не является производным от IListener< const SConsolePacket& >.

Очевидно, мое понимание наследования в С++ не совсем верно. У меня сложилось впечатление, что IListener<const SControlPacket&> и IListener<const SConsolePacket&> - это два разных типа и что объявления функций

addListener(IListener<const SConsolePacket&>* listener)

и

addListener(IListener<const SControlPacket&>* listener)

объявить две отдельные функции, которые выполняют две отдельные функции в зависимости от (отличного) другого типа вводимого параметра. Кроме того, у меня сложилось впечатление, что указатель на IRigidBody также является указателем на IListener<const SControlPacket&> и что при вызове addListener( m_model ) компилятор должен понимать, что я вызываю вторую из двух вышеперечисленных функций.

Я даже пробовал приводить m_model вот так:

m_controller->addListener(
        static_cast<IListener<const SControlPacket&>*>(m_model) );

но все равно получаю эту ошибку. Я не могу себе представить, насколько неоднозначны эти функции. Кто-нибудь может пролить свет на этот вопрос?

P.S. Я знаю, как заставить функцию быть однозначной, сделав это:

m_controller->ISource<const SControlPacket&>::addListener( m_model );

Мне просто кажется, что это ужасно нечитабельно, и я бы предпочел не делать этого.

Редактировать... шучу. Очевидно, это не решает проблему, поскольку приводит к ошибке компоновщика:

CModelTesterGui.cpp:1312: undefined reference to `utility::ISource<aerobat::SControlPacket const&>::addListener(utility::IListener<SControlPacket const&>*)'

person cheshirekow    schedule 21.08.2009    source источник
comment
Какова связь между SControlPacket и SConsolePacket?   -  person GRB    schedule 21.08.2009
comment
Не могли бы вы добавить, почему последняя строка m_controller->ISource<const SControlPacket&>::addListener( m_model ); устраняет неоднозначность вызова? Если функции перегружены, они должны быть в одном классе. Где объявлены эти функции?   -  person Johannes Schaub - litb    schedule 21.08.2009
comment
@GRB Нет никаких отношений. Оба являются структурами, происходящими из ничего. @litb Я ошибался в этом. Это заставило его скомпилировать, но оказалось, что это приводит к ошибке ссылки, когда компоновщик пытается найти ISource‹...›::addListener(...), который является чисто виртуальным. Теперь я очень смущен. Когда вы говорите «декларированный», я предполагаю, что вы имеете в виду «определенный». Они определены в конкретных классах, производных от IController.   -  person cheshirekow    schedule 21.08.2009
comment
@cheshirekow, я имею в виду объявленный, а не определенный. Выполнение a.B::f запрещает механизм виртуального вызова. Затем должно существовать определение чистой виртуальной функции, если вы называете ее так. Для поиска по имени важны только объявления. В этом случае использование объявления может остановить эти неоднозначности.   -  person Johannes Schaub - litb    schedule 21.08.2009
comment
@litb, понял. Вы на месте. Спасибо и за ответ!   -  person cheshirekow    schedule 21.08.2009


Ответы (1)


Похоже, ваша ситуация такова:

struct A {
  void f();
};

struct B {
  void f(int);
};

struct C : A, B { };

int main() { 
  C c; 
  c.B::f(1); // not ambiguous
  c.f(1);    // ambiguous
}

Второй вызов f неоднозначен, потому что при поиске имени он находит функции в двух разных областях видимости базового класса. В этой ситуации поиск неоднозначен — они не перегружают друг друга. Исправление состоит в том, чтобы использовать объявление using для каждого имени члена. Поиск найдет имена в области C и не будет искать дальше:

struct C : A, B { using A::f; using B::f; };

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

struct controller : ISource<const SConsolePacket&>, ISource<const SControlPacket&> {
  using ISource<const SConsolePacket&>::addListener;
  using ISource<const SControlPacket&>::addListener;
};

Теперь два имени находятся в одной области видимости, и теперь они могут перегружать друг друга. Поиск теперь остановится на классе контроллера, не углубляясь в две ветви базового класса.

person Johannes Schaub - litb    schedule 21.08.2009
comment
НИЦЦА! Так вот для чего эти штуки. Я не думаю, что когда-либо действительно понимал использование объявлений (следствие не совсем понимания поиска имени). Большое Вам спасибо. В качестве примечания я фактически поместил объявление using в интерфейс IController. Думаю, это подходящее место для него. - person cheshirekow; 21.08.2009
comment
По крайней мере, в g++ два использования не разрешены. Вы либо выбираете A::f, либо B::f в производном классе C. - person Dingle; 24.05.2011
comment
@Dingle, вероятно, старая версия g++ - fwiw (для недавних пользователей), 4.7.2 отлично справляется с двумя вариантами использования - person kfmfe04; 20.03.2013
comment
Ну и небольшое уточнение, что делать в случае множественного вариативного наследования? - person Nevermore; 01.04.2016
comment
Так почему же стандарт требует, чтобы компилятор вскинул руки вместо разрешения перегрузки в исходном случае? - person LB--; 31.08.2016
comment
@lb- я думаю, это то, что они считали лучшим компромиссом с точки зрения согласованности, удобства использования и безопасности. Если у вас есть вложенные пространства имен и во внутреннем пространстве имен объявлена ​​функция с тем же именем, что и во внешнем пространстве имен, они также не будут перегружены (согласованность). И представьте, если вы добавите в базовый класс функцию, имя которой случайно совпадает с именем производного класса. Может нарушить код, использующий производный класс (безопасность), если функции перегружены. Впрочем, это только догадки. - person Johannes Schaub - litb; 31.08.2016