2015-07-31 4 views
2

При чтении Barry's answer в Check if a given type has a inner template rebind, я подумал:Почему void_t необходимо проверить наличие типа элемента?

Зачем нам void_t вообще?

Почему не не работает?

#include <iostream> 

template <typename X, typename Y = X> 
struct has_rebind : std::false_type {}; 

template <typename X> 
struct has_rebind<X, typename X::template rebind<int>> : std::true_type {}; 

struct A { }; 
struct B { template <typename > struct rebind { }; }; 

int main() { 
    std::cout << has_rebind<A>::value << std::endl; 
    std::cout << has_rebind<B>::value << std::endl; 
} 

выход

0 
0 

demo

+2

[Как работает 'void_t'] (http://stackoverflow.com/q/27687389/3953764) –

ответ

5

В этих строках:

template <typename X, typename Y = X> 
struct has_rebind : std::false_type {}; 

template <typename X> 
struct has_rebind<X, typename X::template rebind<int>> : std::true_type {}; 

тип по умолчанию (X) и O Вы не указали специализацию (typename X::template rebind<int>) должны быть одного типа. Поскольку пустота довольно хорошо «по умолчанию тип манекен», он часто используется как тип по умолчанию, и мы используем void_t изменить то, что было дано в специализации

+1

Я пропустил факт« того же типа », спасибо, что указали это. –

3

Ваш пример не работает, потому что has_rebind<A> и has_rebind<B> оба имеют один шаблон аргумент, поэтому второй аргумент по умолчанию: has_rebind<A,A> и has_rebind<B,B>. Теперь вы предоставляете специализацию has_rebind<X, X::rebind<int>>, но эта специализация не используется при создании экземпляра has_rebind<B,B>.

Вам понадобится что-то вроде typename Y = typename X::template rebind<int>, чтобы вызвать специализацию, но это не скомпилировано для A.

8

void_t является взломом.

Как шаблон класс специализация шаблона согласования работы является то, что основной шаблон определяет вид (типа или значение) каждый аргумент и значение по умолчанию:

template<class A, class B=A, class C=void> 
struct whatever {}; 

Для вас, чтобы вызвать whatever<?...>, вы должны соответствовать действительному аргументы основной специализации whatever<?...>.

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

template<class T> 
struct whatever<T*, T*, void> { 

список template<?...> аргумент здесь не сопоставляется: что только предоставляет список «свободных переменных». сопоставление шаблонов входит в список шаблонов шаблонов после whatever имя класса. Каждый аргумент сопоставляется по очереди с аргументами, отправленными на whatever, и из этого шаблона, соответствующего «свободным переменным» (class T выше).

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

template<class T> 
struct whatever<T*, typename std::add_const<T>::type*, void> { 

второй аргумент является зависимым типом (от шаблон add_const), поэтому нельзя сопоставить шаблон. В общем случае результат some_template<T>::type может быть полным неинъекционным вычислением Turing: стандарт C++ не требует, чтобы он был инвертирован, к счастью для авторов компилятора.

Компилятор не пытается - вместо этого, он пытается определить T от других аргументов шаблона, а затем заменяет T в течение этого аргумента, и проверяет, что он совпадает с типами передаваемых whatever.

Фокус в том, что сбой замены (в непосредственном контексте) не вызывает ошибку . Если у std::add_const<T> не было поля с именем ::type, вместо генерации ошибки вместо этого он сказал бы: «Ну, этот шаблон не соответствует». Это отбросило бы эту специализацию как жизнеспособную из множества кандидатов.

Эта функция была добавлена ​​еще в первые дни C++, поскольку функция шаблона будет соответствовать жадности даже основным типов, и попытку использовать производные типы (например, iterator::value_type) потерпит неудачу, если вы сдали int в iterator. С помощью этой функции, тот факт, что int не ::value_type будет означать «это не действует перегрузка некоторого шаблона функции», а не ошибка синтаксиса при разрешении перегрузки. Фактически, функции шаблона, где почти бесполезно без него, если происходит перегрузка, поэтому они добавили SFINAE - сбой замены не является ошибкой.

После того, как он появился, люди начали злоупотреблять его. void_t пытается использовать его.

template<class T> 
struct whatever<T*, T*, void_t<decltype(std::declval<T>().hello_world())>> { 

void_t берет свое аргумент типа (ов), и отбрасывает их, и производит voidв зависимом контексте (таким образом блокируя выражение от используемого вывести T). Он делает это таким образом, чтобы сначала оценивать аргументы типа , что означает, что если созданный тип вызывает сбой в непосредственном контексте, вы получаете сбой замены.

Точку в том, что тип производства не имеет значения, когда вы кормите его через void_t. Возможно, мы даже не знаем, какой тип t.hello_world() должен быть, когда мы определяем основную специализацию whatever<?...>. Поэтому мы отбрасываем его и превращаем в void. Для соответствия специализации тип должен совпадать. Поэтому мы устанавливаем тип void в основной специализации, а в специализациях мы выполняем тест, затем передаем его void_t, чтобы выбросить результат типа.

Мы можем также использовать std::enable_if_t<?> с выражением времени компиляции bool, который также производит тип void тогда и только тогда, когда что bool верно (и по иным причинам не в непосредственном контексте).

В некотором смысле void_t отображает действительные выражения типа void и недопустимые выражения типа для смены подстановки.enable_if_t карты true - void и false - к смене замены. В обоих случаях вам нужен тип void по специализации, чтобы «потреблять» результат и правильно соответствовать.

Оба они очень полезны в коде SFINAE, который представляет собой мощную функцию, позволяющую принимать решения о том, какую специализацию использовать на основе чего-то другого, кроме простого сопоставления шаблонов.


Последний я проверил, this requirement was not explicitly in the standard! Правила SFINAE применяются к ошибкам подстановки шаблонов, и все (включая писателей компилятора и стандартные авторы) просто предполагали, что они применяются к отказам замены шаблона класса. Конечно, стандарт достаточно прост, чтобы читать, я, вероятно, просто пропустил его.

В какой-то момент различные составители не согласны с тем, что должно было сделать именно то, что template<class...>using void_t=void;. Некоторые из них потерпят неудачу, если выраженное выражение является сменой замены: другие отметили бы, что выражение будет отброшено и отбросить его перед проверкой на сбой замены. Это было выяснено в отчете о дефектах.

+0

Ничего себе, это один полный ответ! Я не знал о вашей первой сноске, мне было бы интересно, если кто-то подтвердит или покажет, являются ли правила SFINAE для шаблонных классов стандартными или нет. – Caninonos

+1

@Caninonos http://stackoverflow.com/q/30676839/1774667 – Yakk

Смежные вопросы