Как работает " void t
я смотрел выступление Уолтера Брауна на Cppcon14 о современном программировании шаблонов (Часть I,Часть II), где он представил свою void_t техника SFINAE.
пример:
Учитывая простой шаблон переменной, который оценивается в void если все аргументы шаблона формата:
template< class ... > using void_t = void;
и следующий признак, который проверяет наличие переменной-члена с именем :
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
я пытался понять, почему и как это работает. Поэтому крошечный пример:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1.has_member< A >
has_member< A , void_t< decltype( A::member ) > >
decltype( A::member )сформирован
void_t<>является допустимым и оценивается вvoid
has_member< A , void >и поэтому он выбирает специализированный шаблон
has_member< T , void >и оценивает вtrue_type
2.has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::memberне существует
decltype( B::member )плохо сформирован и молча терпит неудачу (sfinae)
has_member< B , expression-sfinae >так что этот шаблон отбрасывается
- компилятор обнаруживает
has_member< B , class = void >С void в качестве аргумента по умолчанию
has_member< B >значениеfalse_type
вопросы:
1. Правильно ли я это понимаю?
2. Уолтер Браун утверждает, что аргумент по умолчанию должен быть точно такого же типа, как и в void_t чтобы он работал. Почему? (Я не понимаю, почему эти типы должны совпадать, не только любой тип по умолчанию работает?)
2 ответов:
когда вы пишите
has_member<A>::value, компилятор ищет имяhas_memberи находит первичный шаблон класса, то есть, это объявление:template< class , class = void > struct has_member;(в OP это написано как определение.)
список аргументов шаблона
<A>сравнивается со списком параметров шаблона этого основного шаблона. Поскольку основной шаблон имеет два параметра, но вы указали только один, оставшийся параметр по умолчанию используется в шаблоне по умолчанию аргумент:void. Это как если бы вы написалиhas_member<A, void>::value.теперь список параметров шаблона сравнивается с любыми специализациями шаблона
has_member. Только если специализация не соответствует, определение основного шаблона используется в качестве резервного. Поэтому учитывается частичная специализация:template< class T > struct has_member< T , void_t< decltype( T::member ) > > : true_type { };компилятор пытается сопоставить аргументы шаблона
A, voidс шаблонами, определенными в частичной специализации:Tиvoid_t<..>по одиночке. Во-первых, выполняется вывод аргумента шаблона. Частичная специализация выше по-прежнему является шаблоном с шаблоном-параметрами, которые должны быть "заполнены" аргументами.первая модель,
T, позволяет компилятору вывести шаблон-параметрT. Это тривиальный вывод, но рассмотрим такой шаблон, какT const&, где мы еще могли вывестиT. По шаблонуTи аргумент шаблонаA, то вывестиTбудетA.во втором паттерне
void_t< decltype( T::member ) >шаблон-параметрTпоявляется в контексте, где он не может быть выведен из любого аргумента шаблона. Для этого есть две причины:
выражение внутри
decltypeявно исключается из вычета аргумента шаблона. Я думаю, это потому, что он может быть сколь угодно сложной.даже если мы использовали шаблон без
decltypeкакvoid_t< T >, тогда вычитаниеTпроисходит на разрешенном шаблоне псевдонима. То есть мы разрешаем шаблон псевдонима и затем пытаемся вывести типTиз полученного шаблона. Однако результирующий шаблонvoid, который не зависит отTи поэтому не позволяет нам найти конкретный тип дляT. Это похоже на математическую задачу о попытке инвертировать постоянную функцию (в математическом смысле этих терминов).шаблон вывод аргумента завершен(*), теперь вывести аргументы шаблона подставляются. Это создает специализацию, которая выглядит следующим образом:
template<> struct has_member< A, void_t< decltype( A::member ) > > : true_type { };тип
void_t< decltype( A::member ) > >теперь можно оценить. Он хорошо формируется после замены, следовательно, нет Замещение Отказ происходит. Получаем:template<> struct has_member<A, void> : true_type { };теперь мы можем сравнить список параметров шаблона этой специализации с аргументами шаблона, предоставленными в оригинал
has_member<A>::value. Оба типа точно совпадают, поэтому выбрана эта частичная специализация.С другой стороны, когда мы определяем шаблон:
template< class , class = int > // <-- int here instead of void struct has_member : false_type { }; template< class T > struct has_member< T , void_t< decltype( T::member ) > > : true_type { };мы в конечном итоге с той же специализации:
template<> struct has_member<A, void> : true_type { };но наш список аргументов шаблона для
has_member<A>::valueсейчас<A, int>. Аргументы не совпадают с параметрами специализации,и основной шаблон выбирается в качестве резервного.
(*) Стандартный, IMHO путает, включает в себя процесс подстановки и сопоставления явно указанных аргументов шаблона в вычет аргумента шаблона
// specialized as has_member< T , void > or discarded (sfinae) template<class T> struct has_member<T , void_t<decltype(T::member)>> : true_type { };что выше специализация существует только тогда, когда она хорошо сформирована, так когда
decltype( T::member )является допустимым и не неоднозначным. специализация-это так, дляhas_member<T , void>как указано в комментарии.когда вы пишите
has_member<A>, этоhas_member<A, void>из-за аргумента шаблона по умолчанию.и у нас есть специализация на
has_member<A, void>(поэтому наследовать отtrue_type) но у нас нет специализацииhas_member<B, void>(поэтому мы используем значение по умолчанию : наследовать отfalse_type)
Comments