Множественное наследование приводит к ложной неоднозначной перегрузке виртуальной функции - PullRequest
7 голосов
/ 04 ноября 2019

В этом примере классы Foo и Bar предоставляются из библиотеки. Мой класс Baz наследуется от обоих.

struct Foo
{
    void do_stuff (int, int);
};

struct Bar
{
    virtual void do_stuff (float) = 0;
};

struct Baz : public Foo, public Bar
{
    void func ()
    {
        do_stuff (1.1f); // ERROR HERE
    }
};

struct BazImpl : public Baz
{
    void do_stuff (float) override {};
};

int main ()
{
    BazImpl () .func ();
}

Я получаю ошибку компиляции reference to ‘do_stuff’ is ambiguous, которая мне кажется ложной, поскольку сигнатуры двух функций совершенно разные. Если бы do_stuff было не виртуальным, я мог бы вызвать Bar::do_stuff для устранения его неоднозначности, но это нарушает полиморфизм и вызывает ошибку компоновщика.

Могу ли я заставить func вызвать виртуальный do_stuff без переименованиявещи

Ответы [ 2 ]

10 голосов
/ 04 ноября 2019

Вы можете сделать это:

struct Baz : public Foo, public Bar
{
    using Bar::do_stuff;
    using Foo::do_stuff;
    //...
}

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

На самом деле это не имеет ничего общего с виртуальными функциями. Следующий пример имеет ту же ошибку GCC 9.2.0 error: reference to 'do_stuff' is ambiguous:

struct Foo
{
    void do_stuff (int, int){}
};

struct Bar
{
    void do_stuff (float) {}
};

struct Baz : public Foo, public Bar
{
    void func ()
    {
        do_stuff (1.1f); // ERROR HERE
    }
};

Возможный связанный вопрос

2 голосов
/ 05 ноября 2019

Поиск имени и разрешение перегрузки различаются. Имя должно быть сначала найдено в области видимости, то есть мы должны найти X, чтобы имя do_stuff было разрешено до X::do_stuff - независимо от использования имени, - а затем разрешение перегрузки выбирает между различнымиобъявления X::do_stuff.

Процесс НЕ должен идентифицировать все такие видимые случаи A::do_stuff, B::do_stuff и т. д., а затем выполнить разрешение перегрузки среди объединения этого. Вместо этого для имени должна быть указана одна область.

В этом коде:

struct Baz : public Foo, public Bar
{
    void func ()
    {
        do_stuff (1.1f); // ERROR HERE
    }
};

Baz не содержит имени do_stuff, поэтому можно искать базовые классы,Но имя встречается в двух разных базах, поэтому поиск имени не может определить область. Мы никогда не добираемся до разрешения перегрузки.

Предлагаемое исправление в другом ответе работает, потому что оно вводит имя do_stuff в область действия Baz, а также вводит 2 перегрузки для имени. Таким образом, поиск имени определяет, что do_stuff означает Baz::do_stuff, а затем разрешение перегрузки выбирается из двух функций, которые известны как Baz::do_stuff.


В качестве shadowing другое следствие поиска имени (не само по себе правило). Поиск имени выбирает внутреннюю область, и поэтому все, что находится во внешней области, не совпадает.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...