Namespaces
Variants
Views
Actions

if statement

From cppreference.com
< cpp‎ | language
Revision as of 07:59, 18 May 2023 by RobStewart (Talk | contribs)

 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static

Special member functions
Templates
Miscellaneous
 
 

Conditionally executes another statement.

Used where code needs to be executed based on a run-time or compile-time(since C++17) condition, or whether the if statement is evaluated in a manifestly constant-evaluated context(since C++23).

Contents

Syntax

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)
1) if statement without an else branch
2) if statement with an else branch
3) consteval if statement without an else branch
4) consteval if statement with an else branch
attr - (since C++11) any number of attributes
constexpr - (since C++17) if present, the statement becomes a constexpr if statement
init-statement - (since C++17) either
(since C++23)
Note that any init-statement must end with a semicolon ;, which is why it is often described informally as an expression or a declaration followed by a semicolon.
condition - one of
statement-true - any statement (often a compound statement), which is executed if condition evaluates to true
statement-false - any statement (often a compound statement), which is executed if condition evaluates to false
compound-statement - any compound statement, which is executed if the if-statement
statement - any statement (must be a compound statement, see below), which is executed if the if-statement

Explanation

If the condition yields true after conversion to bool, statement-true is executed.

If the else part of the if statement is present and condition yields false after conversion to bool, statement-false is executed.

In the second form of if statement (the one including else), if 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 doesn't have an else).

#include <iostream>
 
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 << " <= 2\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_cast<Derived*>(bp1)) // cast fails, returns nullptr
        p->df(); // not executed
 
    if (auto p = dynamic_cast<Derived*>(bp2)) // cast succeeds
        p->df(); // executed
}

Output:

2 is not greater than 2
2 > 1 and 1 <= 2
df()

If statements with initializer

If init-statement is used, the if statement is equivalent to

{
init_statement
attr (optional) if constexpr(optional) ( condition )
statement-true

}

or

{
init_statement
attr (optional) if constexpr(optional) ( condition )
statement-true
else
statement-false

}

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 statements.

std::map<int, 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";
    }
}
(since C++17)

Constexpr if

The statement that begins with if constexpr is known as the constexpr if statement.

In a constexpr if statement, the value of 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 the value is 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:

template<typename T>
auto get_value(T t)
{
    if constexpr (std::is_pointer_v<T>)
        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.

template<typename 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.
}

Note: an example where the condition remains value-dependent after instantiation is a nested template, e.g.

template<class T>
void g()
{
    auto lm = [](auto p)
    {
        if constexpr (sizeof(T) == 1 && sizeof p == 1)
        {
            // this condition remains value-dependent after instantiation of g<T>
        }
    };
}

Note: the discarded statement cannot be ill-formed for every possible specialization:

template<typename T>
void f()
{
    if constexpr (std::is_arithmetic_v<T>)
        // ...
    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 CWG2518 for such a catch-all statement is a type-dependent expression that is always false:

template<typename>
inline constexpr bool dependent_false_v = false;
 
template<typename T>
void f()
{
    if constexpr (std::is_arithmetic_v<T>)
        // ...
    else {
        // workaround before CWG2518
        static_assert(dependent_false_v<T>, "Must be arithmetic");
    }
}

Labels (goto targets, case labels, and default:) appearing in a substatement of a constexpr if can only be referenced (by switch or goto) in the same substatement.

Note: 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.

(since C++17)

Consteval if

The statement that begins with if consteval is known as the consteval if statement. In a consteval if statement,

attr (optional) if !(optional) consteval compound-statement (3) (since C++23)
attr (optional) if !(optional) consteval compound-statement else statement (4) (since C++23)

both compound-statement and statement (if any) must be compound statements.

If statement is not a compound statement, it will still be treated as a part of the consteval if statement (and thus results in a compilation error):

constexpr 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.

A case or default label appearing within a consteval if statement shall be associated with a switch statement within the same if statement. A label declared in a substatement of a consteval if statement shall only be referred to by a statement in the same substatement.

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:

  • if !consteval {/*stmt*/} is equivalent to if consteval {} else {/*stmt*/}.
  • if !consteval {/*stmt-1*/} else {/*stmt-2*/} is equivalent to if consteval {/*stmt-2*/} else {/*stmt-1*/}.

compound-statement in a consteval if statement (or statement in the negative form) is in an immediate function context, so the call need not be a constant expression.

#include <cmath>
#include <cstdint>
#include <cstring>
#include <iostream>
 
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';
}
(since C++23)

Notes

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

is the same as

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.

(since C++17)
(until C++23)

switch and goto are not allowed to jump into a branch of constexpr if statement or a consteval if statement(since C++23).

(since C++17)
Feature-test macro Value Std Feature
__cpp_if_constexpr 201606L (C++17) constexpr if
__cpp_if_consteval 202106L (C++23) consteval if

Keywords

if, else, constexpr, consteval

Defect reports

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)

See also

detects whether the call occurs within a constant-evaluated context
(function) [edit]
C documentation for if statement