Как работает специализация шаблона:
Существует основная специализация . Этот в основном определяет аргументы и значения по умолчанию.
template <typename T, typename = void>
Это шаблонная часть вашей основной специализации. Требуется один тип, затем другой тип по умолчанию void
.
Это «интерфейс» вашего шаблона.
template <typename T>
[...] <T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>> [...]
здесь вторичная специализация .
В этом случае template <typename T>
принципиально отличается. В основной специализации он определил интерфейс; здесь он определяет «переменные», которые используются ниже.
Тогда у нас есть часть, где мы делаем сопоставление с образцом. Это после имени шаблона (в данном случае это переменная). Переформатировано для здравомыслия:
<
T,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(T())
)
>,
T
>
>
теперь мы можем видеть структуру. Есть два аргумента, соответствующих двум аргументам в основной специализации.
Первый - T
. Теперь это соответствует имя в основной специализации, но это ничего не значит. Это похоже на вызов функции make_point(int x, int y)
с переменными x,y
- это может быть y,x
или m,n
, а make_point
не волнует.
Мы представили совершенно новую переменную T
в этой специализации. Затем мы связываем это с первым аргументом.
Второй аргумент сложен. Достаточно сложный, чтобы он находился в «не выводимом контексте». Как правило, аргументы специализации шаблона выводятся из аргументов, передаваемых шаблону, как определено в основной специализации; не выводимые аргументы не являются.
Если мы сделаем some_template< Foo >
, то при сопоставлении типа T
с Foo
получим ... Foo
. Довольно простой образец совпадения. Допускаются более подходящие сопоставления с образцом, например специализация, которая занимает T*
; это не соответствует some_template<int>
, но соответствует some_template<int*>
с T=int
.
Не выведенные аргументы не участвуют в этой игре. Вместо этого подключаются аргументы, которые соответствуют do , и генерируется результирующий тип. И если и только если это соответствует типу, переданному шаблону в этом слоте, специализация совпадает.
Итак, давайте рассмотрим, что происходит, мы передаем vec
в качестве первого аргумента my_temp
Сначала перейдем к основной специализации
template<typename T, typename=void>
my_temp
сейчас my_temp<vec>
имеет аргумент по умолчанию. Становится my_temp<vec,void>
.
Затем мы проверяем каждую другую специализацию, чтобы увидеть, совпадает ли какая-либо из них; если никто этого не делает, мы остаемся основной специализацией.
Другая специализация:
template<typename T>
[...] my_temp<
T,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(T())
)
>,
T
>
>[...]
с [...]
для вещей, которые не имеют значения.
Хорошо, первый аргумент связан с T
. Ну, первый аргумент vec
, так что это легко. Подставляем:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
is_floating_point_v
<
decltype
(
details::X(vec())
)
>,
vec
>
>[...]
затем оцените:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
is_floating_point_v
<
double
>,
vec
>
>[...]
и более:
template<typename T>
[...] my_temp<
vec,
enable_if_t
<
true,
vec
>
>[...]
и более:
template<typename T>
[...] my_temp<
vec,
vec
>[...]
хорошо, помните, что мы пытались сравнить с my_temp<vec,void>
. Но эта специализация оценивается в my_temp<vec,vec>
, и те не совпадают. Отклонено.
Удалите ,T
из enable_if
или сделайте его ,void
(то же самое), и последняя строка приведенного выше аргумента станет my_temp<vec,void>
соответствует my_temp<vec,void>
, и вторичная специализация будет выбрана вместо первичной один.
Это сбивает с толку. Один и тот же синтаксис означает принципиально разные вещи в первичной и вторичной специализации. Вы должны понимать сопоставление с образцом аргументов шаблона и не выводимых контекстов.
И то, что вы обычно получаете, это кто-то, использующий его, как волшебный черный ящик, который вы копируете.
Волшебный черный ящик - шаблоны - полезны, потому что они означают, что вам не нужно думать о деталях того, как вы туда попали. Но понимание соответствия шаблонных аргументов шаблона, выведенного и не выведенного контекстов, а также различий между первичной и вторичной специализациями является ключом к пониманию того, почему черный ящик работает.