Я смотрел выступление Уолтера Брауна на Cppcon14 о современном программировании шаблонов ( часть I , часть II ), где он представил свою void_t
технику SFINAE.
Пример:
дан простой шаблон переменной, который оценивает, правильно void
ли сформированы все аргументы шаблона:
template< class ... > using void_t = void;
и следующая черта, которая проверяет существование переменной- члена, называемой member :
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 ) > >
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
для его работы. Это почему? (Я не понимаю, почему эти типы должны совпадать, разве не какой-либо тип по умолчанию работает?)
has_member<A,int>::value
. Тогда частичная специализация, которая оценивается как,has_member<A,void>
не может соответствовать. Следовательно, он должен бытьhas_member<A,void>::value
или, с синтаксическим сахаром, аргументом по умолчанию типаvoid
.has_member< T , class = void >
дефолтеvoid
. Предполагая, что эта черта будет использоваться только с 1 аргументом шаблона в любое время, тогда аргумент по умолчанию может быть любого типа?template <class, class = void>
наtemplate <class, class = void_t<>>
. Так что теперь мы можем делать все, что захотим, сvoid_t
реализацией шаблона псевдонима :)Ответы:
1. Основной шаблон класса
Когда вы пишете
has_member<A>::value
, компилятор ищет имяhas_member
и находит основной шаблон класса, то есть это объявление:(В ОП это написано как определение.)
Список аргументов шаблона
<A>
сравнивается со списком параметров шаблона этого первичного шаблона. Поскольку основной шаблон имеет два параметра, но поставляется только один, оставшийся параметр по умолчанию для аргумента шаблона по умолчанию:void
. Это как если бы ты написалhas_member<A, void>::value
.2. Специализированный шаблон класса
Теперь список параметров шаблона сравнивается с любыми специализациями шаблона
has_member
. Только в случае отсутствия соответствия специализации определение основного шаблона используется в качестве запасного варианта. Так что частичная специализация учитывается:Компилятор пытается сопоставить аргументы шаблона
A, void
с шаблонами, определенными в частичной специализации:T
иvoid_t<..>
один за другим. Сначала выполняется вывод аргумента шаблона. Приведенная выше частичная специализация по-прежнему представляет собой шаблон с параметрами-шаблонами, которые должны быть «заполнены» аргументами.Первый шаблон
T
позволяет компилятору выводить параметр шаблонаT
. Это тривиальный вывод, но рассмотрим пример, подобный томуT const&
, где мы все еще можем сделать выводT
. Для рисункаT
и аргумент шаблонаA
, мы выводимT
бытьA
.Во втором
void_t< decltype( T::member ) >
шаблоне параметр шаблонаT
появляется в контексте, в котором он не может быть выведен из какого-либо аргумента шаблона.Аргумент шаблона вычет закончен (*) , в настоящее время на выведенные аргументы шаблона заменяются. Это создает специализацию, которая выглядит следующим образом:
Тип
void_t< decltype( A::member ) >
теперь может быть оценен. Он хорошо сформирован после замещения, следовательно, сбоев замещения не происходит. Мы получили:3. Выбор
Теперь мы можем сравнить список параметров шаблона этой специализации с аргументами шаблона, предоставленными оригиналу
has_member<A>::value
. Оба типа совпадают, поэтому эта частичная специализация выбрана.С другой стороны, когда мы определяем шаблон как:
Мы заканчиваем с той же специализацией:
но наш список аргументов шаблона на
has_member<A>::value
данный момент<A, int>
. Аргументы не соответствуют параметрам специализации, и основной шаблон выбирается как запасной вариант.(*) Стандарт, ИМХО сбивает с толку, включает процесс замены и сопоставление явно определенных аргументов шаблона в процессе вывода аргументов шаблона . Например (post-N4296) [temp.class.spec.match] / 2:
Но это не просто означает, что все параметры шаблона частичной специализации должны быть выведены; это также означает, что подстановка должна быть успешной и (как кажется?) аргументы шаблона должны соответствовать (замещенным) параметрам шаблона частичной специализации. Обратите внимание, что я не совсем понимаю, где Стандарт определяет сравнение между списком замещенных аргументов и предоставленным списком аргументов.
источник
Эта выше специализация существует только тогда, когда она правильно сформирована, то есть, когда
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
)источник