После того, как у меня возникли проблемы с другим дизайном, я решил создать make-класс-оболочку, чтобы добавить перегрузки к некоторым функциям-членам базового класса, если и только если в базовом классе еще нет жизнеспособных перегрузок. По сути, вот что я пытаюсь сделать:
template<typename T>
struct wrapper: T
{
using T::foo;
template<typename Arg>
auto foo(Arg) const
-> std::enable_if_t<not std::is_constructible<Arg>::value, bool>
{
return false;
}
};
struct bar
{
template<typename Arg>
auto foo(Arg) const
-> bool
{
return true;
}
};
В этом простом примере wrapper
добавляет перегруженный foo
только если один из базового класса не является жизнеспособным (я упростил std::enable_if
самой простой вещи; оригинал включал идиому обнаружения). Однако g ++ и clang ++ не согласны. Возьмите следующее main
:
int main()
{
assert(wrapper<bar>{}.foo(0));
}
G ++ хорошо с этим: foo
от wrapper<bar>
SFINAEd, поэтому он использует один из bar
вместо. С другой стороны, Clang ++, кажется, предполагает тот wrapper<bar>::foo
всегда тени bar::foo
, даже когда СНАЧАЛА Вот сообщение об ошибке:
main.cpp:30:26: error: no matching member function for call to 'foo' assert(wrapper<bar>{}.foo(0)); ~~~~~~~~~~~~~~~^~~ /usr/include/assert.h:92:5: note: expanded from macro 'assert' ((expr) \ ^ /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with Arg = int] using enable_if_t = typename enable_if<_Cond, _Tp>::type; ^ 1 error generated.
Итак, кто прав? Должен ли этот код быть отклонен, как это делает clang ++, или он должен работать и вызывать bar::foo
?
Рассмотрим §10.2:
В объявлении установлено, используя декларированиес заменены набором
назначенные члены, которые не являются скрытый или переопределены членами
производный класс (7.3.3),
И §7.3.3 идет
Когда используя декларирование приносит имена из базового класса в область видимости производного класса, […] шаблоны функций-членов в производном классе переопределяют и / или скрывают функции-члены и шаблоны функций-членов с одинаковыми именами, Параметр типа-лист (8.3.5 [dcl.fct]), cv-квалификация и реф-классификатор (если есть) в базовом классе (а не конфликтующие).
Очевидно, что единственное отличие в вашем примере заключается в типах возвращаемых данных. Таким образом Clang является правильным, и GCC прослушивается.
Формулировка была введена CWG # 1764:
В соответствии с 7.3.3 [namespace.udecl] параграф 15,
Когда объявление использования переносит имена из базового класса в область видимости производного класса, […]
Алгоритм поиска имени области действия приведен в 10.2.
[class.member.lookup], однако, не реализует это требование;
нет ничего, что удаляло бы скрытого члена базового класса (заменяя
используя декларирование, в соответствии с пунктом 3) из набора результатов.
Резолюция была перенесена в DR в феврале 2014 года, поэтому, возможно, GCC еще не внедрила ее.
Как упомянуто в ответе @ TartanLlama, вы можете представить коллегу для обработки другого случая. Нечто подобное
template <typename Arg, typename=std::enable_if_t<std::is_constructible<Arg>{}>>
decltype(auto) foo(Arg a) const
{
return T::foo(a);
}
Это похоже на ошибку; при явном объявлении использования Clang должен учитывать базовую перегрузку. Там нет причин для этого не.
Ваш код, тем не менее, неверен, потому что нет никакой причины предпочитать ограниченную версию базовой версии, когда обе действительны, поэтому я считаю, что вы должны получить ошибку неоднозначности, если передаете что-то, что не может быть использовано по умолчанию.
Как говорит @SebastianRedl, это похоже на лягушатник (Правка: похоже, что мы были неправильно).
Независимо от того, какой компилятор верен, возможный обходной путь будет определять две версии wrapper<T>::foo
: один, когда T::foo
не существует, что обеспечивает пользовательскую реализацию; и один для того, когда это делает, который передает вызов к базовой версии:
template<typename T>
struct wrapper: T
{
template<typename Arg, typename U=T>
auto foo(Arg) const
-> std::enable_if_t<!has_foo<U>::value, bool>
{
return false;
}
template<typename Arg, typename U=T>
auto foo(Arg a) const
-> std::enable_if_t<has_foo<U>::value, bool>
{
return T::foo(a); //probably want to perfect forward a
}
};