Ответ 1
Общая идея
Вместо перечисления всех допустимых типов функций, таких как примерная реализация на cpprefereence.com, в этой реализации перечислены все типы, которые не являются функциями, а затем разрешаются только true
, если ни один из них не сопоставляется.
Список нефункциональных типов состоит из (снизу вверх):
- Классы и союзы (включая абстрактные типы)
- Все, что может быть возвращено функцией (включая
void
и ссылочные типы) - Типы массивов
Тип, который не соответствует ни одному из этих не-функциональных типов, является типом функции. Обратите внимание, что std::is_function
явно рассматривает вызываемые типы, такие как lambdas или классы, с оператором вызова функции как не являющиеся функциями.
is_function_impl_
Мы предоставляем одну перегрузку функции is_function_impl
для каждого из возможных не-функциональных типов. Объявления функций могут быть немного сложны для синтаксического анализа, поэтому давайте разбить его на примере классов и объединений:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
Эта строка объявляет шаблон функции is_function_impl_
, который принимает один аргумент типа priority_tag<3>
и возвращает ссылку на массив из 4 char
s. Как принято с древних дней C, синтаксис объявления становится ужасно запутанным наличием типов массивов.
Этот шаблон функции принимает два аргумента шаблона. Первый - это просто неограниченный T
, а второй - указатель на элемент T
типа int
. Часть int
здесь не имеет значения, т.е. это будет работать даже для T
, у которых нет элементов типа int
. Тем не менее, это то, что это приведет к синтаксической ошибке для T
, которая не относится к классу или типу объединения. Для тех других типов попытка создания шаблона функции приведет к ошибке замены.
Подобные трюки используются для перегрузок priority_tag<2>
и priority_tag<1>
, которые используют свои аргументы второго шаблона для формирования выражений, которые компилируются для T
как допустимые типы возвращаемых функций или типы массивов соответственно. Только перегрузка priority_tag<0>
не имеет такого сдерживающего второго параметра шаблона и поэтому может быть создана с любым T
.
В целом мы объявляем четыре разных перегрузки для is_function_impl_
, которые отличаются их входным аргументом и возвращаемым типом. Каждый из них принимает в качестве аргумента другой тип priority_tag
и возвращает ссылку на массив char с различным уникальным размером.
Отправка тегов в is_function
Теперь, при создании экземпляра is_function
, он создает is_function_impl
с помощью T
. Обратите внимание, что поскольку мы предоставили четыре различные перегрузки для этой функции, здесь должно иметь место перегрузочное разрешение. И поскольку все эти перегрузки являются функциональными шаблонами, это означает, что SFINAE имеет шанс зайти.
Итак, для функций (и только функций) все перегрузки будут сбой, кроме самого общего с priority_tag<0>
. Так почему же инстанцирование не всегда разрешает эту перегрузку, если она самая общая? Из-за входных аргументов наших перегруженных функций.
Обратите внимание, что priority_tag
создается таким образом, что priority_tag<N+1>
публично наследуется от priority_tag<N>
. Теперь, поскольку is_function_impl
вызывается здесь с priority_tag<3>
, эта перегрузка является лучшим совпадением, чем другие для разрешения перегрузки, поэтому сначала будет проверяться. Только если это не удается из-за ошибки замены, проверяется следующее наилучшее совпадение, которое представляет собой перегрузку priority_tag<2>
. Мы продолжаем таким образом, пока не найдем перегрузку, которая может быть создана, или мы достигнем priority_tag<0>
, которая не ограничена и всегда будет работать. Поскольку все нефункциональные типы покрываются более высокими перегрузками prio, это может произойти только для типов функций.
Оценка результата
Теперь мы проверяем размер типа, возвращаемого вызовом is_function_impl_
, для оценки результата. Помните, что каждая перегрузка возвращает ссылку на массив char разного размера. Поэтому мы можем использовать sizeof
, чтобы проверить, какая перегрузка была выбрана, и только установить результат на true
, если мы достигли перегрузки priority_tag<0>
.