"if constexpr()" 和 "if()" 的区别

102
if constexpr()if()之间有什么区别?
我可以在哪里和什么时候使用它们?

3
起点:http://en.cppreference.com/w/cpp/language/ifif语句是C++中的控制流语句之一,用于根据条件执行代码块。if语句包含一个条件表达式和要执行的语句块。如果条件表达式的结果为真,则执行语句块。否则,它将跳过该块并继续执行程序。在if语句中可以使用else关键字来指定当条件不成立时应执行的代码块。else语句也可以与另一个if语句结合使用,形成嵌套的if-else语句。在C++中,条件表达式可以是任何返回布尔值的表达式,包括比较运算符、逻辑运算符和函数调用。除此之外,还有一些特殊情况的if语句,例如if constexpr语句和if consteval语句,它们具有特殊的语义和用途。 - Jesper Juhl
1
@Rakete1111 constexpr if() 和 if constexpr() 是一样的吗? - msc
3
不,P0292R1比P0128R1更新。还有一个说明,即P0292R1修订了P0128R1,因此if constexpr是新语法,而不是无效的constexpr if - Rakete1111
2个回答

129
唯一的区别是if constexpr在编译时评估,而if不会。这意味着分支可以在编译时被拒绝,因此永远不会被编译。
想象一下,你有一个函数叫做length,它返回一个数字的长度,或者返回一个具有.length()函数的类型的长度。你不能在一个函数中完成这个任务,否则编译器会报错:
template<typename T>
auto length(const T& value) noexcept {
    if (std::is_integral<T>::value) { // is number
        return value;
       }
    else{
        return value.length();
    }
}

int main() noexcept {
    int a = 5;
    std::string b = "foo";

    std::cout << length(a) << ' ' << length(b) << '\n'; // doesn't compile
}

错误信息:
main.cpp: In instantiation of 'auto length(const T&) [with T = int]':
main.cpp:16:26:   required from here
main.cpp:9:16: error: request for member 'length' in 'val', which is of non-class type 'const int'
     return val.length();
            ~~~~^~~~~~

那是因为当编译器实例化length时,函数会变成这样:
auto length(const int& value) noexcept {
    if (std::is_integral<int>::value) { // is number
        return value;
    else
        return value.length();
}

value 是一个 int,因此没有 length 成员函数,所以编译器报错。编译器无法看到对于一个 int,该语句永远不会被执行,但无论如何,编译器无法保证。

现在你可以专门为 length 进行特化,但对于许多类型(比如在这种情况下 - 每个具有 length 成员函数的数字和类),这会导致大量重复的代码。SFINAE 也是一种解决方案,但它需要多个函数定义,这使得代码比下面的方式冗长得多。

使用 if constexpr 而不是 if 意味着分支(std::is_integral<T>::value)将在编译时评估,并且如果为 true,则会丢弃其他所有分支(else ifelse)。如果为 false,则检查下一个分支(这里是 else),如果为 true,则丢弃其他所有分支,依此类推...

template<typename T>
auto length(const T& value) noexcept {
    if constexpr (std::integral<T>::value) { // is number
        return value;
    else
        return value.length();
}

现在,当编译器实例化length时,它会变成这样:
int length(const int& value) noexcept {
    //if constexpr (std::is_integral<int>::value) { this branch is taken
        return value;
    //else                           discarded
    //    return value.length();     discarded
}

std::size_t length(const std::string& value) noexcept {
    //if constexpr (std::is_integral<int>::value) { discarded
    //    return value;                   discarded
    //else                           this branch is taken
        return value.length();
}

所以这两个重载是有效的,代码会成功编译。


3
请注意,如果不使用if constexpr,您可以调用一个模板结构体,并(部分)特化它以避免代码重复。因此,在我看来,if constexpr的主要好处是更符合程序员的“自然”外观。 - Daniel Jour
不用理会我之前的评论,我看到你确实包含了我注意到的事实(“...如果条件为true,那么其他分支(else ifelse)都会被丢弃。”)。 - dfrib
2
在编程中,将以下内容从英语翻译为中文。仅返回已翻译的文本:if (std::integral<T>::value) { // is number 应该在开头改为 std::is_integral<T>::value - user3882729

79

普通的if语句:

  • 每次控制流到达时都会对其条件进行求值
  • 确定要执行哪个子语句,跳过另一个子语句
  • 无论实际选中哪个子语句,都需要两个子语句都是格式正确的

if constexpr语句:

  • 一旦提供了所有必要的模板参数,它的条件在编译时只会被求值一次
  • 确定要编译哪个子语句,丢弃另一个
  • 不需要弃用的子语句是格式正确的

参数不一定要是模板参数,对吧?另外,为什么只有两个子语句?不能有更多吗? - Rakete1111
5
一个有效的常量表达式只能依赖于先前已初始化的变量值。所以一旦编译器看到一个 if constexpr 语句,它应该已经拥有足够的信息来评估条件。唯一的例外是当这个语句出现在一个模板中时,在这种情况下可能没有足够的信息,直到模板参数被指定。常量表达式不能依赖于普通函数参数,因为它们的值在编译时不可知。 - Brian Bi
5
if语句总是有一个或两个子语句。如果你使用了if ... else if ... else ...,那么实际上有两个if语句,一个嵌套在另一个中,每个语句都有两个子语句。 - Brian Bi
4
在模板之外,一个废弃语句会被完全检查。if constexpr不能替代#if预处理指令。 - Tarquiscani
这可能有点离题和异端邪说,但是,为什么不只用 if 呢?编译器可以决定它是 if constexpr 还是普通的 if。因此,这个东西不会影响程序员,但规范必须改变:如果一个 if 条件在编译时评估,则只编译 ifelse 代码。但对于程序员来说,这是透明的。只是好奇。 - Chameleon
使用术语“编译”而不是“实例化”是否正确?请参见此链接 - User 10482

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接