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, используя непересекающиеся наборы типов:
- Указатели в c ++ после удаления
- Производятся ли конструкторы перемещения автоматически?
- regex заменить на callback в c ++ 11?
- Как объединить значения hashа в C ++ 0x?
- Включена ли новая функция инициализации члена C ++ 11 при объявлении, что списки инициализации устарели?
struct Derived: public Base, public Base {};
Я надеялся, что при вызове, например,
Derived().foo();
компилятор будет определять, какой базовый class использовать, потому что он является SFINAE’d из того, который не содержит int
. Тем не менее, как GCC 4.9, так и Clang 3.5 жалуются на двусмысленный вызов.
Мой вопрос тогда в два раза:
- Почему компилятор не может решить эту двусмысленность (общий интерес)?
- Что я могу сделать, чтобы выполнить эту работу, без необходимости писать
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 {};
- разрешение std :: chrono :: high_resolution_clock не соответствует измерениям
- C ++ 11 «перегруженная lambda» с вариационным шаблоном и захватом переменной
- Могут ли использоваться шаблоны lambda?
- constexpr не компилируется в VC2013
- Disambiguate перегруженный указатель функции участника передается как параметр шаблона
- Списки инициализаторов и RHS операторов
- Почему мы копируем, а затем двигаемся?
- Вызов функции для каждого вариационного аргумента шаблона и массива
Вот более простой пример:
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
.
Альтернативно, чтобы 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
и using Base
to Derived
и теперь он компилируется.
Протестировано с помощью clang-3.4
и gcc-4.9