Функция SFINAEd-out скрывает явно импортированную перегрузку из базового класса

После того, как у меня возникли проблемы с другим дизайном, я решил создать 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?

11

Решение

Рассмотрим §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);
}

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

7

Другие решения

Это похоже на ошибку; при явном объявлении использования Clang должен учитывать базовую перегрузку. Там нет причин для этого не.

Ваш код, тем не менее, неверен, потому что нет никакой причины предпочитать ограниченную версию базовой версии, когда обе действительны, поэтому я считаю, что вы должны получить ошибку неоднозначности, если передаете что-то, что не может быть использовано по умолчанию.

2

Как говорит @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
}
};

Live Demo

1
По вопросам рекламы [email protected]