Used where code needs to be executed based on a condition , or whether the if statement is evaluated in a manifestly constant-evaluated context (since C++23) .
attr (optional) if constexpr (optional) ( init-statement (optional) condition ) statement-true | (1) | |
attr (optional) if constexpr (optional) ( init-statement (optional) condition ) statement-true else statement-false | (2) | |
attr (optional) if ! (optional) consteval compound-statement | (3) | (since C++23) |
attr (optional) if ! (optional) consteval compound-statement else statement | (4) | (since C++23) |
Note that any init-statement must end with a semicolon. This is why it is often described informally as an expression or a declaration followed by a semicolon.
A condition can either be an expression or a simple declaration.
When control reaches condition, the condition will yield a value, which is used to determine which branch the control will go to.
If condition is an expression, the value it yields is the the value of the expression contextually converted to bool . If that conversion is ill-formed, the program is ill-formed.
If condition is a simple declaration, the value it yields is the value of the decision variable (see below) contextually converted to bool . If that conversion is ill-formed, the program is ill-formed.
The declaration has the following restrictions:
The decision varaiable of the declaration is the declared variable.
The declaration has the following restrictions:
The decision variable of the declaration is the invented variable e introduced by the declaration.
If the condition yields true , statement-true is executed.
If the else part of the if statement is present and condition yields false , statement-false is executed.
If the else part of the if statement is present and statement-true is also an if statement, then that inner if statement must contain an else part as well (in other words, in nested if statements, the else is associated with the closest if that does not have an else ).
Run this code#include int main() { // simple if-statement with an else clause int i = 2; if (i > 2) std::cout i " is greater than 2\n"; else std::cout i " is not greater than 2\n"; // nested if-statement int j = 1; if (i > 1) if (j > 2) std::cout i " > 1 and " j " > 2\n"; else // this else is part of if (j > 2), not of if (i > 1) std::cout i " > 1 and " j " \n"; // declarations can be used as conditions with dynamic_cast struct Base { virtual ~Base() {} }; struct Derived : Base { void df() { std::cout "df()\n"; } }; Base* bp1 = new Base; Base* bp2 = new Derived; if (Derived* p = dynamic_castDerived*>(bp1)) // cast fails, returns nullptr p->df(); // not executed if (auto p = dynamic_castDerived*>(bp2)) // cast succeeds p->df(); // executed }
2 is not greater than 2 2 > 1 and 1if statements with initializer
If init-statement is used, the if statement is equivalent to
Except that names declared by the init-statement (if init-statement is a declaration) and names declared by condition (if condition is a declaration) are in the same scope, which is also the scope of both statement s.
std::mapint, std::string> m; std::mutex mx; extern bool shared_flag; // guarded by mx int demo() { if (auto it = m.find(10); it != m.end()) return it->second.size(); if (char buf[10]; std::fgets(buf, 10, stdin)) m[0] += buf; if (std::lock_guard lock(mx); shared_flag) { unsafe_ping(); shared_flag = false; } if (int s; int count = ReadBytesWithSignal(&s)) { publish(count); raise(s); } if (const auto keywords = {"if", "for", "while"}; std::ranges::any_of(keywords, [&tok](const char* kw) { return tok == kw; })) { std::cerr <"Token must not be a keyword\n"; } }Constexpr if
The statement that begins with if constexpr is known as the constexpr if statement. All substatements of a constexpr if statement are control-flow-limited statements.
In a constexpr if statement, condition must be a contextually converted constant expression of type bool (until C++23) an expression contextually converted to bool , where the conversion is a constant expression (since C++23) .
If condition yields true , then statement-false is discarded (if present), otherwise, statement-true is discarded.
The return statements in a discarded statement do not participate in function return type deduction:
templatetypename T> auto get_value(T t) { if constexpr (std::is_pointer_vT>) return *t; // deduces return type to int for T = int* else return t; // deduces return type to int for T = int }The discarded statement can ODR-use a variable that is not defined:
extern int x; // no definition of x required int f() { if constexpr (true) return 0; else if (x) return x; else return -x; }Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive:
void f() { if constexpr(false) { int i = 0; int *p = i; // Error even though in discarded statement } }If a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated.
templatetypename T, typename . Rest> void g(T&& p, Rest&& . rs) { // . handle p if constexpr (sizeof. (rs) > 0) g(rs. ); // never instantiated with an empty argument list }The condition remains value-dependent after instantiation is a nested template:
templateclass T> void g() { auto lm = [=](auto p) { if constexpr (sizeof(T) == 1 && sizeof p == 1) { // this condition remains value-dependent after instantiation of g, // which affects implicit lambda captures // this compound statement may be discarded only after // instantiation of the lambda body } }; }The discarded statement cannot be ill-formed for every possible specialization:
templatetypename T> void f() { if constexpr (std::is_arithmetic_vT>) // . else { using invalid_array = int[-1]; // ill-formed: invalid for every T static_assert(false, "Must be arithmetic"); // ill-formed before CWG2518 } }The common workaround before the implementation of CWG issue 2518 for such a catch-all statement is a type-dependent expression that is always false :
templatetypename> inline constexpr bool dependent_false_v = false; templatetypename T> void f() { if constexpr (std::is_arithmetic_vT>) // . else { // workaround before CWG2518 static_assert(dependent_false_vT>, "Must be arithmetic"); } }A typedef declaration or alias declaration (since C++23) can be used as the init-statement of a constexpr if statement to reduce the scope of the type alias.
This section is incomplete Reason: no example |
The statement that begins with if consteval is known as the consteval if statement. All substatements of a consteval if statement are control-flow-limited statements.
statement must be a compound statement, and it will still be treated as a part of the consteval if statement even if it is not a compound statement (and thus results in a compilation error):
Run this codeconstexpr void f(bool b) { if (true) if consteval {} else ; // error: not a compound-statement // else not associated with outer if }
If a consteval if statement is evaluated in a manifestly constant-evaluated context, compound-statement is executed. Otherwise, statement is executed if it is present.
If the statement begins with if ! consteval , the compound-statement and statement (if any) must both be compound statements. Such statements are not considered consteval if statements, but are equivalent to consteval if statements:
compound-statement in a consteval if statement (or statement in the negative form) is in an immediate function context, in which a call to an immediate function needs not to be a constant expression.
Run this code#include #include #include #include constexpr bool is_constant_evaluated() noexcept { if consteval { return true; } else { return false; } } constexpr bool is_runtime_evaluated() noexcept { if not consteval { return true; } else { return false; } } consteval std::uint64_t ipow_ct(std::uint64_t base, std::uint8_t exp) { if (!base) return base; std::uint64_t res{1}; while (exp) { if (exp & 1) res *= base; exp /= 2; base *= base; } return res; } constexpr std::uint64_t ipow(std::uint64_t base, std::uint8_t exp) { if consteval // use a compile-time friendly algorithm { return ipow_ct(base, exp); } else // use runtime evaluation { return std::pow(base, exp); } } int main(int, const char* argv[]) { static_assert(ipow(0, 10) == 0 && ipow(2, 10) == 1024); std::cout ipow(std::strlen(argv[0]), 3) '\n'; }
If statement-true or statement-false is not a compound statement, it is treated as if it were:
if (x) int i; // i is no longer in scope
if (x) { int i; } // i is no longer in scope
The scope of the name introduced by condition , if it is a declaration, is the combined scope of both statements' bodies:
if (int x = f()) { int x; // error: redeclaration of x } else { int x; // error: redeclaration of x }
If statement-true is entered by goto or longjmp , condition is not evaluated and statement-false is not executed.
Built-in conversions are not allowed in the condition of a constexpr if statement, except for non-narrowing integral conversions to bool .
Feature-test macro | Value | Std | Feature |
---|---|---|---|
__cpp_if_constexpr | 201606L | (C++17) | constexpr if |
__cpp_if_consteval | 202106L | (C++23) | consteval if |
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 631 | C++98 | the control flow was unspecified if the first substatement is reached via a label | the condition is not evaluated and the second substatement is not executed (same as in C) |