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;
. Некоторые из них потерпят неудачу, если выраженное выражение является сменой замены: другие отметили бы, что выражение будет отброшено и отбросить его перед проверкой на сбой замены. Это было выяснено в отчете о дефектах.
[Как работает 'void_t'] (http://stackoverflow.com/q/27687389/3953764) –