if 语句
有条件地执行另一条语句。
用于需要基于运行时或编译时 (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 起) | |||||||
属性 | - | (C++11 起) 任意数量的属性 | ||
constexpr
|
- | (C++17 起) 出现时,该语句即为 constexpr if 语句 | ||
初始化语句 | - | (C++17 起) 下列之一:
| ||
条件 | - | 下列之一: | ||
true分支语句 | - | 任意语句(通常是复合语句),当 条件 求值为 true 时执行 | ||
false分支语句 | - | 任意语句(通常是复合语句),当 条件 求值为 false 时执行 | ||
复合语句 | - | 任意复合语句,在以下场合执行:
| ||
语句 | - | 任意语句(只能是复合语句,见下文),在以下场合执行:
|
解释
如果 条件 在转换到 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 语句等价于
或
但 初始化语句 所声明的名字(如果 初始化语句 是声明)和 条件 所声明的名字(如果 条件 是声明)处于同一作用域中,同时也是两条 语句 所在的作用域。 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以 在 constexpr if 语句中,条件 的值必须是可按语境转换到 被舍弃语句中的 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...); // 始终不会对空实参列表实例化。 } 在模板外,被舍弃语句受到完整的检查。 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 目标、 注意: 可以将 typedef 声明和别名声明 (C++23 起)用作 constexpr if 语句的初始化语句以减少类型别名的作用域。
|
(C++17 起) |
consteval if以 即使 语句 不是复合语句,它也会被视为 consteval if 语句的一部分(因此产生编译错误): 运行此代码 constexpr void f(bool b) { if (true) if consteval {} else ; // 错误:不是复合语句,else 不与外层 if 关联 } 如果在明显常量求值语境中求值 consteval if 语句,那么执行 复合语句 。否则在 语句 存在时执行它。 在 consteval if 语句中出现的 如果语句以
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 }
如果通过 goto
或 longjmp 进入 true分支语句,那么不对 条件 求值,也不执行 false分支语句。
(C++17 起) (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 的行为一致) |
参阅
(C++20) |
检测调用是否在常量求值的语境内发生 (函数) |