if 语句

来自cppreference.com
< cpp‎ | language

有条件地执行另一条语句。

用于需要基于运行时或编译时 (C++17 起)条件或者 if 语句是否在明显常量求值语境下求值 (C++23 起)执行的代码。

语法

属性(可选) if constexpr(可选) ( 初始化语句(可选) 条件 ) true分支语句 (1)
属性(可选) if constexpr(可选) ( 初始化语句(可选) 条件 ) true分支语句 else false分支语句 (2)
属性(可选) if !(可选) consteval 复合语句 (3) (C++23 起)
属性(可选) if !(可选) consteval 复合语句 else 语句 (4) (C++23 起)
1) 不带 else 分支的 if 语句
2) 带 else 分支的 if 语句
3) 不带 else 分支的 consteval if 语句
4) 带 else 分支的 consteval if 语句
属性 - (C++11 起) 任意数量的属性
constexpr - (C++17 起) 出现时,该语句即为 constexpr if 语句
初始化语句 - (C++17 起) 下列之一:
(C++23 起)
注意,任何 初始化语句 都会以分号 ; 结束,这是它常被非正式地描述成后随分号的表达式或声明的原因。
条件 - 下列之一:
true分支语句 - 任意语句(通常是复合语句),当 条件 求值为 true 时执行
false分支语句 - 任意语句(通常是复合语句),当 条件 求值为 false 时执行
复合语句 - 任意复合语句,在以下场合执行:
  • if 语句在明显常量求值语境下求值,如果 consteval 未前附 !if consteval
  • if 语句不在明显常量求值语境下求值,如果 consteval 前附 !if !consteval
语句 - 任意语句(只能是复合语句,见下文),在以下场合执行:
  • if 语句不在明显常量求值语境下求值,如果 consteval 未前附 !if consteval else
  • if 语句在明显常量求值语境下求值,如果 consteval 前附 !if !consteval else

解释

如果 条件 在转换到 bool 后产生 true,那么执行 true分支语句

如果 if 语句存在 else 部分,且 条件 在转换到 bool 后产生 false,那么执行 false分支语句

在 if 语句的第二形式(包含 else)中,如果 true分支语句 也是 if 语句,那么内层 if 语句必须也含有 else 部分(换言之,在嵌套 if 语句中 else 关联到最近的尚未有 else 的 if)。

#include <iostream>
 
int main()
{
    // 带 else 子句的简单 if 语句
    int i = 2;
    if (i > 2)
        std::cout << i << " 大于 2\n";
    else
        std::cout << i << " 不大于 2\n";
 
    // 嵌套 if 语句
    int j = 1;
    if (i > 1)
        if (j > 2)
            std::cout << i << " > 1 且 " << j << " > 2\n";
        else // 此 else 属于 if (j > 2),而不是 if (i > 1)
            std::cout << i << " > 1 且 " << j << " <= 2\n";
 
    // 以下声明可用于含 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)) // 转型失败,返回 nullptr
        p->df(); // 不执行
 
    if (auto p = dynamic_cast<Derived*>(bp2)) // 转型成功
        p->df(); // 执行
}

输出:

2 不大于 2
2 > 1 且 1 <= 2
df()


带初始化器的 if 语句

如果使用 初始化语句,那么 if 语句等价于

{
初始化语句
if constexpr(可选) ( 条件 )
true分支语句

}

{
初始化语句
if constexpr(可选) ( 条件 )
true分支语句
else
false分支语句

}

初始化语句 所声明的名字(如果 初始化语句 是声明)和 条件 所声明的名字(如果 条件 是声明)处于同一作用域中,同时也是两条 语句 所在的作用域。

std::map<int, std::string> m;
std::mutex mx;
extern bool shared_flag; // 由 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 不能是关键词\n");
    }
}
(C++17 起)

constexpr if

if constexpr 开始的语句被称为 constexpr if 语句

在 constexpr if 语句中,条件 的值必须是可按语境转换到 bool 类型的经转换常量表达式 (C++23 前)按语境转换到 bool 的表达式,其中转换为常量表达式 (C++23 起)。如果它的值是 true,那么舍弃 false分支语句(如果存在),否则舍弃 true分支语句

被舍弃语句中的 return 语句不参与函数返回类型推导:

template<typename T>
auto get_value(T t)
{
    if constexpr (std::is_pointer_v<T>)
        return *t; // 对 T = int* 推导返回类型为 int
    else
        return t;  // 对 T = int 推导返回类型为 int
}

被舍弃语句可以 ODR 使用未定义的变量:

extern int x; // 不需要 x 的定义
 
int f()
{
    if constexpr (true)
        return 0;
    else if (x)
        return x;
    else
        return -x;
}

如果 constexpr if 语句在模板实体内出现,且如果 条件 在实例化后不是值待决的,那么外围模板被实例化时不会实例化被舍弃语句。

template<typename T, typename... Rest>
void g(T&& p, Rest&&... rs)
{
    // ... 处理 p
    if constexpr (sizeof...(rs) > 0)
        g(rs...); // 始终不会对空实参列表实例化。
}

在模板外,被舍弃语句受到完整的检查。if constexpr 不是 #if 预处理指令的替代品:

void f()
{
    if constexpr (false)
    {
        int i = 0;
        int *p = i; // 在被舍弃语句中仍为错误
    }
}

注意:实例化后仍为值待决的一个例子是嵌套模板,例如:

template<class T>
void g()
{
    auto lm = [](auto p)
    {
        if constexpr (sizeof(T) == 1 && sizeof p == 1)
        {
            // 此条件在 g<T> 实例化后仍为值待决的
        }
    };
}

注意:被舍弃语句不能对所有特化均非良构:

template<typename T>
void f()
{
    if constexpr (std::is_arithmetic_v<T>)
        // ...
    else {
        using invalid_array = int[-1]; // 非良构:对于所有 T 都非法
        static_assert(false, "必须是算术类型"); // CWG2518 前非良构
    }
}

实现 CWG2518 前对这种万应语句的常用变通方案,是一条始终为 false 的类型待决表达式:

template<class>
inline constexpr bool dependent_false_v = false;
 
template<typename T>
void f()
{
    if constexpr (std::is_arithmetic_v<T>)
        // ...
    else {
        // CWG2518 前的变通方案
        static_assert(dependent_false_v<T>, "必须是算术类型"); // OK
    }
}

在 constexpr if 的子语句中出现的标号(goto 目标case 标号和 default:),只能在同一子语句中(由 switchgoto)引用。

注意: 可以将 typedef 声明别名声明 (C++23 起)用作 constexpr if 语句的初始化语句以减少类型别名的作用域。

(C++17 起)

consteval if

if consteval 开始的语句被称为 consteval if 语句。在 consteval if 语句中 复合语句语句 (如果存在)必须是复合语句。

即使 语句 不是复合语句,它也会被视为 consteval if 语句的一部分(因此产生编译错误):

constexpr void f(bool b)
{
    if (true)
        if consteval {}
        else ; // 错误:不是复合语句,else 不与外层 if 关联
}

如果在明显常量求值语境中求值 consteval if 语句,那么执行 复合语句 。否则在 语句 存在时执行它。

在 consteval if 语句中出现的 casedefault 标签必须关联到同一 if 语句中的 switch 语句。在 consteval if 语句中声明的标签必须只关联到同一子语句中的语句。

如果语句以 if !consteval 开始,那么 复合语句语句 (如果存在)必须都是复合语句。这种语句不被认为是 consteval if 语句,但它等价于 consteval if 语句:

  • if !consteval {/*语句*/} 等价于 if consteval {} else {/*语句*/}
  • if !consteval {/*语句1*/} else {/*语句2*/} 等价于 if consteval {/*语句2*/} else {/*语句1*/}

consteval if 语句中的 复合语句 (或否定形式中的 语句)是立即函数语境,在其中对立即函数的调用不需要是常量表达式。

#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 // 使用编译时友好的算法
    {
        return ipow_ct(base, exp);
    }
    else // 使用运行时求值
    {
        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';
}
(C++23 起)

注解

如果 true分支语句false分支语句 不是复合语句,那么按如同是复合语句一样处理:

if (x)
    int i;
// i 不再在作用域中

与下面相同

if (x)
{
    int i;
}
// i 不再在作用域中

如果 条件 是声明,那么它引入的名字的作用域是两个语句体的合并作用域:

if (int x = f())
{
    int x; // 错误:重复声明了 x
}
else
{
    int x; // 错误:重复声明了 x
}

如果通过 gotolongjmp 进入 true分支语句,那么不对 条件 求值,也不执行 false分支语句

constexpr if 语句的 条件 中不允许内建转换,除了到 bool 的非窄化整数转换

(C++17 起)
(C++23 前)

不允许 switchgoto 跳入 constexpr if 语句或 consteval if 语句 (C++23 起)的分支。

(C++17 起)
功能特性测试宏 标准 注释
__cpp_if_constexpr 201606L (C++17) constexpr if
__cpp_if_consteval 202106L (C++23) consteval if

关键词

if, else, constexpr, consteval

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 631 C++98 未指明通过标签抵达第一子语句时的控制流 不对条件求值,也不执行第二子语句(与 C 的行为一致)

参阅

检测调用是否在常量求值的语境内发生
(函数)