Несколько спецификаций шаблона класса SFINAE с использованием void_t
Являются ли множественные специализации шаблонов классов действительными, когда каждый отличается только между шаблонами, включающими параметры шаблона в невыводимых контекстах?
Обычный пример std::void_t
использует его для определения признака, который показывает, имеет ли тип член typedef
, называемый "type". Здесь используется одна специализация. Это можно было бы расширить, чтобы определить, имеет ли тип либо член typedef
, называемый "type1", либо один, называемый "type2". Код С++ 1z ниже компилируется с помощью GCC, но не Clang. Является ли это законным?
template <class, class = std::void_t<>>
struct has_members : std::false_type {};
template <class T>
struct has_members<T, std::void_t<typename T::type1>> : std::true_type {};
template <class T>
struct has_members<T, std::void_t<typename T::type2>> : std::true_type {};
Ответы
Ответ 1
Существует правило, что частичные специализации должны быть более специализированными, чем первичный шаблон - обе ваши специализации следуют этому правилу. Но нет правила, согласно которому частичные специализации никогда не могут быть двусмысленными. Это более того - если инстанцирование приводит к неоднозначной специализации, программа плохо сформирована. Но эта неоднозначная инстанция должна произойти сначала!
Похоже, что clang страдает от CWG 1558 здесь и очень хочет заменить void
на std::void_t
.
Это CWG 1980 почти точно:
В примере типа
template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();
кажется, что второе объявление f
является повторной декларацией первого, но различимым с помощью SFINAE, то есть эквивалентным, но не функционально эквивалентным.
Если вы используете реализацию non-alias void_t
:
template <class... Ts> struct make_void { using type = void; };
template <class... Ts> using void_t = typename make_void<Ts...>::type;
тогда clang допускает две разные специализации. Конечно, создание экземпляра has_members
для типа, имеющего ошибки type1
и type2
typedefs, но ожидаемого.
Ответ 2
Я не верю, что это правильно или, по крайней мере, не если мы создаем экземпляр has_members с типом, который имеет как тип 1, так и тип 2, в результате будут две специализации, которые
has_members<T, void>
что недействительно. Пока код не будет создан, я думаю, что все в порядке, но clang отвергает его раньше. В g++ ваш сбой с этим прецедентом, после создания экземпляра:
struct X
{
using type1 = int;
using type2 = double;
};
int main() {
has_members<X>::value;
}
Сообщение об ошибке, похоже, не описывает фактическую проблему, но, по крайней мере, излучается:
<source>:20:21: error: incomplete type 'has_members<X>' used in nested name specifier
has_members<X>::value;
^~~~~
Если вы создаете экземпляр с типом, который имеет только тип1 или type2, но не оба,
то g++ компилирует его чисто. Поэтому он возражает против того, что члены присутствуют одновременно, вызывая конфликтующие экземпляры шаблона.
Чтобы получить дизъюнкцию, я думаю, вам нужен код следующим образом:
template <class, class = std::void_t<>>
struct has_members : std::bool_constant<false> {};
template <class T>
struct has_members<T, std::enable_if_t<
std::disjunction<has_member_type1<T>, has_member_type2<T>>::value>> :
std::bool_constant<true> {};
Это предполагает, что у вас есть черты для определения has_member_type1 и has_member_type2, уже записанных.