ядро языка — синтаксисcore language — syntax
Ветвление на этапе компиляции внутри шаблона: недействующая ветка вообще не инстанцируется, поэтому она может быть невалидной для тех типов, для которых не выбрана.Compile-time branching inside a template: the branch not taken isn't instantiated at all, so it may be invalid for the types it's not chosen for.
if constexpr (cond) — форма if, где cond должно быть константным выражением, вычислимым на этапе компиляции. Внутри шаблона компилятор выбирает одну из веток в зависимости от результата cond и инстанцирует только её — вторая ветка для данной конкретной инстанциации шаблона не компилируется вообще, как будто её не существовало.if constexpr (cond) is a form of if where cond must be a constant expression computable at compile time. Inside a template, the compiler picks one branch based on the result of cond and instantiates only that one — the other branch, for that particular template instantiation, isn't compiled at all, as if it didn't exist.
template<typename T>
typename std::enable_if<std::is_same<T, int>::value, void>::type
print(T v) { print_int(v); }
template<typename T>
typename std::enable_if<!std::is_same<T, int>::value, void>::type
print(T v) { print_generic(v); }
// две отдельные перегрузки ради одной ветки логики// two separate overloads for one branch of logic
template<typename T>
void print(T v) {
if constexpr (std::is_same_v<T, int>) {
print_int(v);
} else {
print_generic(v);
}
} // одна функция, ветка выбирается при инстанциации// one function, the branch is picked at instantiation
Выбор кода по типу в шаблонах был возможен и до C++17, но требовал техник, изначально не предназначенных для этой задачи.Selecting code by type in templates was possible before C++17 too, but required techniques that weren't originally designed for the job.
Чтобы выбрать поведение по типу шаблонного параметра, приходилось писать несколько перегрузок одной функции, каждая с enable_if в сигнатуре — сама логика ветвления пряталась в объявлении типа возврата, а не читалась как обычный if в теле функции.To select behavior based on a template parameter's type, you had to write several overloads of the same function, each with enable_if in its signature — the branching logic itself hid in the return-type declaration instead of reading as an ordinary if in the function body.
Альтернатива — принимать дополнительный аргумент-тег (std::true_type/std::false_type) и перегружать функцию по нему — работала, но требовала отдельной вспомогательной функции на каждую ветку и передачи тега на каждый вызов, что многословно ради простого условия.The alternative — accepting an extra tag argument (std::true_type/std::false_type) and overloading on it — worked, but required a separate helper function per branch and passing a tag at every call, which was verbose for a simple condition.
Даже если условие обычного if внутри шаблона зависело от типа и в рантайме выполнялась только одна ветка, компилятор всё равно был обязан успешно скомпилировать ОБЕ ветки для каждой инстанциации — код вроде v.begin() в ветке, которая никогда не исполнится для типа без begin(), всё равно ломал сборку.Even if a plain if's condition inside a template depended on the type and only one branch ran at runtime, the compiler still had to successfully compile BOTH branches for every instantiation — code like v.begin() in a branch that would never execute for a type without begin() still broke the build.
Ключевое отличие от обычного if: невыбранная ветка if constexpr не просто не выполняется в рантайме — она вообще не инстанцируется компилятором для данного набора шаблонных параметров. Это значит, что код в невыбранной ветке может быть некорректен для этих типов (не скомпилировался бы, если бы его инстанцировали), и это не вызовет ошибку — при условии, что ошибка не обнаруживается уже на этапе разбора шаблона в общем виде (например, синтаксическая ошибка сломает компиляцию в любом случае).The key difference from a plain if: the branch if constexpr doesn't take isn't merely skipped at runtime — it's never instantiated by the compiler for that set of template parameters at all. This means code in the untaken branch can be invalid for those types (it wouldn't compile if instantiated) and this won't cause an error — provided the error isn't already detectable while parsing the template in its generic form (a syntax error, for instance, still breaks compilation regardless).
template<typename Container>
auto first_element(Container& c) {
if constexpr (std::is_pointer_v<Container>) {
return *c; // для указателя — только эта ветка инстанцируется// for a pointer — only this branch gets instantiated
} else {
return *c.begin(); // c.begin() не существует у указателя — но код и не инстанцируется// c.begin() doesn't exist for a pointer — but this code is never instantiated
}
}
// first_element(some_int_ptr) — компилируется, потому что вторая ветка отброшена// first_element(some_int_ptr) — compiles, because the second branch is discarded
Условие cond вычисляется на этапе компиляции ровно как в static_assert или значении шаблонного non-type параметра — оно должно быть constant expression, но может зависеть от шаблонных параметров окружающей функции/класса (например, std::is_same_v<T, int>, где T — параметр шаблона).The cond is evaluated at compile time exactly as in a static_assert or a non-type template parameter's value — it must be a constant expression, but it may depend on the enclosing function/class's template parameters (e.g. std::is_same_v<T, int>, where T is a template parameter).
Если if constexpr используется не внутри шаблона (или условие не зависит от шаблонных параметров), обе ветки по-прежнему должны быть валидным кодом — просто одна из них не сгенерирует машинный код, потому что условие константно известно уже при первом разборе. Отбрасывание невалидного кода работает только для веток, зависящих от шаблонных параметров текущей инстанциации.If if constexpr is used outside a template (or the condition doesn't depend on template parameters), both branches must still be valid code — one of them simply won't generate machine code because the condition is already known at the first pass. Discarding invalid code only works for branches that depend on the current instantiation's template parameters.
Легко забыть слово constexpr при написании ветвления по типу и получить ошибку компиляции о невалидном коде в "ненужной" ветке — потому что без constexpr компилятор обязан инстанцировать обе ветки. И наоборот: увидев if constexpr в чужом коде, легко не заметить, что условие обязано быть constant expression, а не произвольным рантайм-условием.It's easy to forget the constexpr keyword when writing type-based branching and get a compile error about invalid code in the "unneeded" branch — because without constexpr the compiler must instantiate both branches. Conversely, seeing if constexpr in someone else's code, it's easy to miss that the condition must be a constant expression, not an arbitrary runtime one.
Как и обычный if, if constexpr может обойтись без else. Но если функция возвращает значение из обеих логических веток, явный else обычно нагляднее, чем полагаться на "выпадание" в конец функции — особенно когда каждая ветка возвращает значение другого типа.Like a plain if, if constexpr can do without else. But if a function returns a value from both logical branches, an explicit else is usually clearer than relying on falling through to the end of the function — especially when each branch returns a different type.