C ++ 11: Disambiguate class-member в множественном наследовании

Предположим, что у меня есть этот вариационный базовый class-шаблон:

template  class Base { public: // The member foo() can only be called when its template // parameter is contained within the Types ... pack. template  typename std::enable_if<Contains::value>::type foo() { std::cout << "Base::foo()\n"; } }; 

Член foo() может вызываться только тогда, когда его параметр-шаблон соответствует хотя бы одному из параметров Base (реализация Contains указана внизу в этом сообщении):

 Base().foo(); // fine Base().foo(); // error 

Теперь я определяю производный class, который наследует дважды от Base, используя непересекающиеся наборы типов:

 struct Derived: public Base, public Base {}; 

Я надеялся, что при вызове, например,

 Derived().foo(); 

компилятор будет определять, какой базовый class использовать, потому что он является SFINAE’d из того, который не содержит int . Тем не менее, как GCC 4.9, так и Clang 3.5 жалуются на двусмысленный вызов.

Мой вопрос тогда в два раза:

  1. Почему компилятор не может решить эту двусмысленность (общий интерес)?
  2. Что я могу сделать, чтобы выполнить эту работу, без необходимости писать Derived().Base::foo(); ? EDIT: GuyGreer показал мне, что вызов неоднозначен, когда я добавляю два объявления-объявления. Однако, поскольку я предоставляю базовый class для унаследованного пользователя, это не идеальное решение. Если это вообще возможно, я не хочу, чтобы мои пользователи добавляли эти объявления (которые могут быть довольно многословными и повторяющимися для больших списков типов) к их производным classам.

Реализация Contains :

 template  struct Contains; template  struct Contains: public std::false_type {}; template  struct Contains: public std::true_type {}; template  struct Contains: public Contains {}; 

Вот более простой пример:

 template  class Base2 { public: void foo(T ) { } }; struct Derived: public Base2, public Base2 {}; int main() { Derived().foo(0); // error } 

Причина этого исходит из правил слияния [class.member.lookup]:

В противном случае (т. Е. C не содержит объявления f или результирующий набор объявлений пуст), S (f, C) изначально пуст. Если C имеет базовые classы, вычислите набор поиска для f в каждом подбодете прямого базового classа Bi и объедините каждое такое поисковое множество S (f, Bi) в свою очередь в S (f, C).
– [..]
– В противном случае, если множества объявлений S (f, Bi) и S (f, C) различаются, слияние неоднозначно …

Поскольку наш исходный набор объявлений пуст ( Derived не имеет в нем методов), мы должны слиться со всеми нашими базами, но наши базы имеют разные множества, поэтому слияние не выполняется. Однако это правило явно применяется только в том случае, если набор объявлений C ( Derived ) пуст. Поэтому, чтобы избежать этого, мы делаем его непустым:

 struct Derived: public Base2, public Base2 { using Base2::foo; using Base2::foo; }; 

Это работает, потому что правило применения

В объявлении set -declarations заменяются набором назначенных элементов, которые не скрыты или переопределены членами производного classа (7.3.3),

Там нет комментариев о том, отличаются ли члены – мы фактически просто предоставляем Derived с двумя перегрузками на foo , минуя правила слияния имен имен членов.

Теперь Derived().foo(0) однозначно вызывает Base2::foo(int ) .

Альтернативно, чтобы using для каждой базы явно, вы можете написать коллекционер, чтобы сделать все:

 template  struct BaseCollector; template  struct BaseCollector : Base { using Base::foo; }; template  struct BaseCollector : Base, BaseCollector { using Base::foo; using BaseCollector::foo; }; struct Derived : BaseCollector, Base2> { }; int main() { Derived().foo(0); // OK Derived().foo(std::string("Hello")); // OK } 

Хотя я не могу подробно рассказать, почему это не работает, как я, добавил using Base::foo; и using Base::foo; to Derived и теперь он компилируется.

Протестировано с помощью clang-3.4 и gcc-4.9

  • Почему macros препроцессора злые и какие альтернативы?
  • Как включить поддержку C ++ 11 / C ++ 0x в Eclipse CDT?
  • Когда следует использовать std :: move для возвращаемого значения функции?
  • Возможности C ++ 11 в Visual Studio 2012
  • Как эмулировать инициализацию массива C "int arr = {e1, e2, e3, ...}" поведение с std :: array?
  • Преобразование std :: __ cxx11 :: string в std :: string
  • C ++ 11 эквивалентно увеличению shared_mutex
  • Сравнение трех современных методов c ++ для преобразования целочисленных значений в строки
  • В чем разница между пустым и нулевым std :: shared_ptr в C ++?
  • initializer_list и вывод типа шаблона
  • Почему нет std :: protect?
  • Interesting Posts

    Разница между фреймворком и статической библиотекой в ​​xcode4 и как их называть

    Android: рисование пользовательских фигур

    Как остановить изменение размера окна, когда канал отображения монитора выключен / переключен на другой источник

    Журнал ping и дата не работают

    Word 2016 to pdf – ttf выходит как 72 dpi

    PCA в matlab, который выбирает верхние n компонентов

    Внедрение EmguCV TypeInitializationException

    Удалить столбцы с нулевыми значениями из фрейма данных

    Требуется ли антивирусное программное обеспечение для Mac (OS X); Если это бесплатное решение?

    Как скопировать настройки Chrome без их сброса?

    Ошибка в : цель присвоения расширяется до неязыкового объекта

    Как остановить задачу, запланированную в classе Java.util.Timer

    Как заставить Windows переименовать файл со специальным символом?

    Необходимо ли переопределять .NET Framework после перехода на новую микроархитектуру процессора?

    Подмена MAC-адресов в Windows 7 – 00 как первые две цифры?

    Давайте будем гением компьютера.