шаблоны и библиотекаtemplates and library
Шаблон переменной, а не только функции или класса — 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.
До 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.
template <class T>
struct Pi {
static constexpr T value = T(3.1415926535897932385);
};
Pi<double>::value; // многословно для одной константы// verbose for a single constant
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
Отсутствие variable templates было асимметрией в системе шаблонов, которая на практике оборачивалась лишним кодом:The absence of variable templates was an asymmetry in the template system that translated into extra code in practice:
Язык допускал 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.
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.
Комитет уже смотрел вперёд на 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.
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.
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.
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 в примерах не обязательно синтаксически — 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.