if 语句
有条件地执行另一条语句。
用于需要基于条件或者 if 语句是否在明显常量求值语境下求值(C++23 起)执行的代码。
语法
属性 (可选) if constexpr(可选) ( 初始化语句 (可选) 条件 ) true分支语句
|
(1) | ||||||||
属性 (可选) if constexpr(可选) ( 初始化语句 (可选) 条件 ) true分支语句 else false分支语句
|
(2) | ||||||||
属性 (可选) if !(可选) consteval 复合语句
|
(3) | (C++23 起) | |||||||
属性 (可选) if !(可选) consteval 复合语句 else 语句
|
(4) | (C++23 起) | |||||||
else 分支的 if 语句else 分支的 if 语句else 分支的 consteval if 语句else 分支的 consteval if 语句| 属性 | - | (C++11 起) 任意数量的属性 | ||
constexpr
|
- | (C++17 起) 出现时,该语句是 constexpr if 语句 | ||
| 初始化语句 | - | (C++17 起) 下列之一:
注意,任何初始化语句 都应以分号 | ||
| 条件 | - | 条件 | ||
| true分支语句 | - | 任意当条件 求值为 true 时会执行的语句
| ||
| false分支语句 | - | 当条件 求值为 false 时会执行的语句
| ||
| 复合语句 | - | 当此 if 语句在明显常量求值语境下求值时会执行的复合语句(consteval 前附 ! 的情况下条件变成“不在这种语境下求值”)
| ||
| 语句 | - | 当此 if 语句不在明显常量求值语境下求值时会执行的语句(consteval 未前附 ! 的情况下条件变成“在这种语境下求值”)
|
条件
|
(C++26 起) |
- 如果在语法上它既可以解析为表达式,则作为表达式处理,否则作为结构化绑定声明以外的(C++26 起)声明处理。
当控制抵达条件 时,条件会产生一个值,该值用于确定控制会继续前往哪个分支。
表达式
如果条件 是表达式,那么它产生的值就是该表达式按语境转换到 bool 的值。如果该转换非良构,那么程序也非良构。
声明
如果条件 是简单声明,那么它产生的值是决定变量(见下文)按语境转换到 bool 的值。如果该转换非良构,那么程序也非良构。
非结构化绑定声明
声明具有以下限制:
- 语法符合以下形式:
|
(C++11 前) |
|
(C++11 起) |
这种声明的决定变量就是它声明的变量。
结构化绑定声明声明具有以下限制: 这种声明的决定变量是由声明引入的虚设变量 |
(C++26 起) |
分支选择
如果条件 产生了 true,那么就会执行true分支语句。
如果 if 语句存在 else 部分,且条件 产生了 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()
带初始化器的
|
|||||||||||||||||||||||||||||||||||||||||||||||
{
|
|||||||||
或
{
|
|||||||||
但初始化语句 声明的名字(如果初始化语句 是声明)和条件 声明的名字(如果条件 是声明)处于同一作用域中,同时也是两条语句 所在的作用域。
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");
}
}
constexpr if以 在 constexpr if 语句中,条件 的值必须是可按语境转换到 如果条件 的值是 被舍弃语句中的 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;
}
在模板外,被舍弃语句会受到完整的检查。 void f()
{
if constexpr (false)
{
int i = 0;
int *p = i; // 在被舍弃语句中仍为错误
}
}
如果 constexpr if 语句在模板化实体内出现,且如果条件 在实例化后不是值待决的,那么外围模板被实例化时不会实例化被舍弃语句。 template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs)
{
// ... 处理 p
if constexpr (sizeof...(rs) > 0)
g(rs...); // 始终不会对空实参列表实例化。
}
实例化后仍为值待决的一个例子是嵌套模板: template<class T>
void g()
{
auto lm = [=](auto p)
{
if constexpr (sizeof(T) == 1 && sizeof p == 1)
{
// 此条件在 g<T> 实例化后仍为值待决的,这会影响 lambda 的隐式捕获
// 在实例化 lambda 函数体后,此复合语句才可能被舍弃
}
};
}
被舍弃语句不能对所有特化均非良构: template<typename T>
void f()
{
if constexpr (std::is_arithmetic_v<T>)
// ...
else {
using invalid_array = int[-1]; // 非良构:对于所有 T 都非法
static_assert(false, "必须是算术类型"); // CWG2518 前非良构
}
}
实现 CWG 问题 2518 前对这种万应语句的常用变通方案,是一条始终为 template<typename>
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
}
}
可以将 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 语句中的复合语句(或否定形式中的语句)是立即函数语境,在其中对立即函数的调用不需要是常量表达式。 运行此代码 #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 前) |
| 功能特性测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__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 的行为一致) |
