шаблоны и библиотекаtemplates and library

variable templates

Шаблон переменной, а не только функции или класса — template<class T> constexpr T pi = T(3.1415926535897932385); задаёт целое семейство констант, по одной на каждый инстанциированный T.A template for a variable, not just a function or a class — template<class T> constexpr T pi = T(3.1415926535897932385); defines an entire family of constants, one per instantiated T.

основа _v-суффиксов из C++17the basis for C++17's _v suffixes компактнее, чем static-член шаблонного классаmore compact than a static member of a template class

Что этоWhat it is

До C++14 шаблонами могли быть только функции и классы (включая структуры и union) — переменная не могла быть параметризована типом напрямую, для той же цели приходилось заводить шаблонный класс со static constexpr-членом. C++14 добавляет третий вид шаблона — variable template: template<class T> T name = initializer;, где каждая инстанциация с конкретным T порождает отдельный объект-переменную своего типа.Before C++14, only functions and classes (including structs and unions) could be templates — a variable couldn't be parameterized by type directly, so the same goal required setting up a template class with a static constexpr member. C++14 adds a third kind of template — the variable template: template<class T> T name = initializer;, where each instantiation for a concrete T produces a separate variable object of its own type.

C++11 — обходной путь через static-член классаC++11 — a workaround via a static class member
template <class T>
struct Pi {
    static constexpr T value = T(3.1415926535897932385);
};

Pi<double>::value;  // многословно для одной константы// verbose for a single constant
C++14 — variable template напрямуюC++14 — a variable template directly
template <class T>
constexpr T pi = T(3.1415926535897932385);

pi<double>;  // 3.141592653589793 (double)
pi<float>;   // 3.1415927f (float) — отдельная инстанциация// 3.1415927f (float) — a separate instantiation

Зачем ввелиWhy it was introduced

Отсутствие variable templates было асимметрией в системе шаблонов, которая на практике оборачивалась лишним кодом:The absence of variable templates was an asymmetry in the template system that translated into extra code in practice:

01

Асимметрия: функции и классы шаблонизируются, переменные — нетAsymmetry: functions and classes could be templated, variables couldn't

Язык допускал template<class T> T max_of(T a, T b) и template<class T> class Box {...}, но не позволял того же для одиночной переменной напрямую — приходилось имитировать её через статический член шаблонного класса, что тянуло за собой лишнюю сущность (сам класс) ради единственного значения.The language allowed template<class T> T max_of(T a, T b) and template<class T> class Box {...}, but didn't allow the same for a single variable directly — you had to simulate it via a static member of a template class, dragging in an extra entity (the class itself) for the sake of a single value.

02

Обходной путь через static-член был избыточно многословным для константThe static-member workaround was excessively verbose for constants

Pi<double>::value требует объявить целый шаблонный класс, чтобы получить одну константу, параметризованную типом — pi<double> выражает то же самое без промежуточной сущности-класса, которая никогда не используется как класс сама по себе.Pi<double>::value requires declaring an entire template class just to get one constant parameterized by type — pi<double> expresses the same thing without an intermediate class entity that's never used as a class in its own right.

03

Подготовка почвы для компактных трейтов стандартной библиотекиLaying the groundwork for compact standard-library traits

Комитет уже смотрел вперёд на type traits вида std::is_same<T, U>::value, которые всегда требовали писать ::value — variable templates создавали возможность для более короткой формы std::is_same_v<T, U>, реализованной как template<class T, class U> constexpr bool is_same_v = is_same<T, U>::value;. Сами _v-трейты стандартизированы позже, в C++17 — но именно variable templates как языковая фича делают их возможными.The committee was already looking ahead to type traits like std::is_same<T, U>::value, which always required writing ::value — variable templates opened up the possibility of the shorter form std::is_same_v<T, U>, implemented as template<class T, class U> constexpr bool is_same_v = is_same<T, U>::value;. The _v traits themselves were standardized later, in C++17 — but it's variable templates as a language feature that make them possible.

Как это работает под капотомHow it works under the hood

Variable template работает так же, как и любой другой шаблон: определение само по себе не создаёт объект, а служит рецептом. Каждая уникальная комбинация параметров шаблона при первом использовании инстанциируется компилятором в отдельный статический объект своего типа — то есть pi<double> и pi<float> это два разных объекта в разных инстанциациях, не один объект с преобразованием типа на лету.A variable template works just like any other template: the definition by itself doesn't create an object, it serves as a recipe. Each unique combination of template parameters is instantiated by the compiler, on first use, into a separate static object of its own type — meaning pi<double> and pi<float> are two distinct objects in separate instantiations, not one object with an on-the-fly type conversion.

template <class T>
constexpr T pi = T(3.1415926535897932385);

template <class T>
constexpr T circle_area(T r) {
    return pi<T> * r * r;  // T выводится из контекста вызова// T is deduced from the call context
}

circle_area(2.0);   // использует pi<double>// uses pi<double>
circle_area(2.0f);  // использует pi<float> — отдельная инстанциация// uses pi<float> — a separate instantiation

// variable template может специализироваться, как и любой шаблон:// a variable template can be specialized, like any template:
template <>
constexpr int pi<int> = 3;  // явная специализация под int// explicit specialization for int

Как и обычная шаблонная функция, variable template может иметь несколько параметров, значения по умолчанию для параметров и явные (или частичные, если это шаблон в контексте класса) специализации — это полноценный шаблон, а не облегчённая версия с урезанными возможностями.Like an ordinary function template, a variable template can have multiple parameters, default parameter values, and explicit (or partial, in a class-template context) specializations — it's a full-fledged template, not a stripped-down version with reduced capabilities.

ГраблиGotchas

_v-трейты (is_same_v и подобные) — это C++17, не C++14_v traits (is_same_v and similar) are C++17, not C++14

Variable templates как языковая фича появились в C++14, но конкретные _v-алиасы стандартной библиотеки (std::is_same_v, std::is_integral_v и т.д.) добавлены в стандартную библиотеку только в C++17 — путать «фичу языка» и «конкретные алиасы, реализованные с её помощью» означает ошибиться при ответе на вопрос о границах стандартов.Variable templates as a language feature arrived in C++14, but the specific standard-library _v aliases (std::is_same_v, std::is_integral_v, etc.) were only added to the standard library in C++17 — conflating "the language feature" with "the specific aliases implemented using it" is a good way to get a standard-boundaries question wrong.

Каждая инстанциация — отдельный статический объект, не общая памятьEvery instantiation is a separate static object, not shared storage

pi<double> и pi<float> не делят память и не приводятся друг к другу неявно — это независимые переменные с разным адресом (если брать адрес не у constexpr, а у обычной variable template без constexpr). Ожидание «одной переменной pi, которая просто отображается в разных типах» — неверная ментальная модель.pi<double> and pi<float> don't share storage and aren't implicitly convertible to each other — they're independent variables with distinct addresses (if you take the address of a non-constexpr variable template rather than a constexpr one). Expecting "a single pi variable that just displays as different types" is the wrong mental model.

Без constexpr variable template тоже валиден, но менее полезенA variable template without constexpr is also valid, just less useful

Ключевое слово constexpr в примерах не обязательно синтаксически — template<class T> T counter; тоже компилируется, — но именно сочетание с constexpr даёт вычисление на этапе компиляции, что и есть основной практический сценарий использования variable templates для констант.The constexpr keyword in the examples isn't syntactically mandatory — template<class T> T counter; also compiles — but it's specifically the combination with constexpr that gives compile-time evaluation, which is the main practical use case for variable templates as constants.