Неоднозначный вызов при многократном наследовании от универсального базового класса

Я хочу клонировать структуру данных, содержащую std::list нескольких типов, и одновременно перемещать некоторые итераторы, чтобы они указывали на новые элементы std::list. Для этого я создал общий тип Translate<T>, представляющий сопоставление std::list<T>::iterator в старом списке с теми, что в новом списке. Затем у меня есть новый класс, наследуемый от Translate<T> для всех необходимых типов T моей структуры данных.

Вот упрощенный пример (с использованием только функции идентификации):

#include <list>

struct T1 {};
struct T2 {};

template<typename T>
class Translate {
    public:
    typename std::list<T>::iterator operator()(typename std::list<T>::iterator x) {
        return x; // more complex in the real world, but doesn't matter here
    }
};

int main() {
    std::list<T1> l1{};
    std::list<T2> l2{};

    class : public Translate<T1>, public Translate<T2> {} tr;
    tr(l1.begin());
}

Это дает мне следующую ошибку компилятора:

mwe.cpp: In function ‘int main()’:
mwe.cpp:19:15: error: request for member ‘operator()’ is ambiguous
  tr(l1.begin());
               ^
mwe.cpp:9:34: note: candidates are: ‘typename std::__cxx11::list<T>::iterator Translate<T>::operator()(typename std::__cxx11::list<T>::iterator) [with T = T2; typename std::__cxx11::list<T>::iterator = std::_List_iterator<T2>]’
  typename std::list<T>::iterator operator()(typename std::list<T>::iterator x) {
                                  ^~~~~~~~
mwe.cpp:9:34: note:                 ‘typename std::__cxx11::list<T>::iterator Translate<T>::operator()(typename std::__cxx11::list<T>::iterator) [with T = T1; typename std::__cxx11::list<T>::iterator = std::_List_iterator<T1>]’

В чем вызов двусмысленный? std::list<T>::iterator не конвертируются друг в друга.

Это работает, если я вручную скопирую реализацию для каждого T в дочерний класс, но это именно то, чего стараются избегать при использовании дженериков и наследования.


person madlaina    schedule 11.08.2020    source источник
comment
Какой компилятор вы используете? И Clang, и MSVS компилируют этот код. Я мог заставить его потерпеть неудачу только с GCC. Похоже на ошибку для меня.   -  person NathanOliver    schedule 11.08.2020
comment
g++ 8.3.0. Вы правы, с clang компилируется нормально, я об этом даже не подумал. Спасибо!   -  person madlaina    schedule 11.08.2020


Ответы (1)


Этот код имеет неправильный формат. Если operator() недоступен в производном классе, то поиск имени учитывает базовый класс только в том случае, если существует ровно базовый класс. Если базовых классов несколько, то ни один из них не учитывается при поиске имени.

Как вы упомянули, вы можете скопировать реализации operator() в производный класс, и это работает, но вы также можете перенести оба имени operator() в производный класс с помощью директивы using, например:

class : public Translate<T1>, public Translate<T2> {
  public:
    using Translate<T1>::operator();
    using Translate<T2>::operator();
} tr;

Вот демонстрация.

person cigien    schedule 11.08.2020
comment
Разве это не означает, что учитываются все базы? - person NathanOliver; 11.08.2020
comment
@NathanOliver Не уверен на 100%, но я думаю, что это означает что процесс слияния неоднозначен в этом случае. Возможно, формулировка также относится к шаблонам. Возможно, это должен быть вопрос языкового юриста. - person cigien; 11.08.2020
comment
Спасибо за Ваш ответ. Действительно, добавление объявления using решает проблему. Тогда это может быть просто сообщение об ошибке GCC, которое нуждается в доработке. Есть ли также способ ввести все члены в производный класс без необходимости называть каждый метод для каждого суперкласса? - person madlaina; 11.08.2020
comment
Да, не уверен. Разрешение перегрузки — сложный зверь. Половину времени, когда я смотрю на этот стандартный раздел, я удивляюсь, как моя голова не взрывается. - person NathanOliver; 11.08.2020
comment
Нет, вы должны ввести каждое имя вручную. - person cigien; 11.08.2020