Подтвердить что ты не робот

Несколько спецификаций шаблона класса 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 {};
4b9b3361

Ответ 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, уже записанных.