c++ - C++17 引入的评估顺序保证是什么?

标签 c++ c++17 operator-precedence

C++17 evaluation order guarantees (P0145) 中投票的含义是什么?在典型的 C++ 代码上?

它对以下内容有何改变?

i = 1;
f(i++, i)

std::cout << f() << f() << f();

f(g(), h(), j());

最佳答案

到目前为止,评估顺序未指定的一些常见情况已在 C++17 中指定并有效。一些未定义的行为现在是未指定的。

i = 1;
f(i++, i)

未定义,但现在未指定。具体来说,没有指定的是 f 的每个参数相对于其他参数的评估顺序。 i++ 可能在 i 之前计算,反之亦然。事实上,它可能会以不同的顺序评估第二次调用,尽管在同一个编译器下。

但是,在执行任何其他参数之前,需要对每个参数进行评估,以完全执行所有副作用。因此,您可能会得到 f(1, 1) (首先评估第二个参数)或 f(1, 2) (首先评估第一个参数)。但是你永远不会得到 f(2, 2) 或任何其他类似的东西。

std::cout << f() << f() << f();

未指定,但它将与运算符优先级兼容,因此 f 的第一次评估将在流中首先出现(示例如下)。

f(g(), h(), j());

仍有未指定的 g、h 和 j 评估顺序。请注意,对于 getf()(g(),h(),j()),规则规定 getf() 将在 g 之前计算, h, j.

还请注意提案文本中的以下示例:

 std::string s = "but I have heard it works even if you don't believe in it"
 s.replace(0, 4, "").replace(s.find("even"), 4, "only")
  .replace(s.find(" don't"), 6, "");

该示例来自 The C++ Programming Language,第 4 版,Stroustrup,并且曾经是未指定的行为,但在 C++17 中它将按预期工作。可恢复函数 (.then( . . . )) 也存在类似问题。

作为另一个示例,请考虑以下内容:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

struct Speaker{
    int i =0;
    Speaker(std::vector<std::string> words) :words(words) {}
    std::vector<std::string> words;
    std::string operator()(){
        assert(words.size()>0);
        if(i==words.size()) i=0;
        // Pre-C++17 version:
        auto word = words[i] + (i+1==words.size()?"\n":",");
        ++i;
        return word;
        // Still not possible with C++17:
        // return words[i++] + (i==words.size()?"\n":",");

    }
};

int main() {
    auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
    std::cout << spk() << spk() << spk() << spk() << spk() ;
}

在 C++14 之前,我们可能(并且将会)得到如下结果

play
no,and,Work,All,

而不是

All,work,and,no,play

请注意,上面的效果与

(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;

但是,在 C++17 之前,不能保证第一个调用会首先进入流。

引用文献:来自 the accepted proposal :

Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions.

Assignment expressions are evaluated from right to left. This includes compound assignments.

Operands to shift operators are evaluated from left to right. In summary, the following expressions are evaluated in the order a, then b, then c, then d:

  1. a.b
  2. a->b
  3. a->*b
  4. a(b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a << b
  8. a >> b

Furthermore, we suggest the following additional rule: the order of evaluation of an expression involving an overloaded operator is determined by the order associated with the corresponding built-in operator, not the rules for function calls.

编辑说明:我的原始答案误解了 a(b1, b2, b3)b1b2b3 的顺序仍未指定。 (谢谢@KABoissonneault,所有评论者。)

但是,(正如@Yakk 指出的那样)这很重要:即使 b1b2b3 是不平凡的表达式,在开始评估其他参数之前,它们中的每一个都被完全评估并绑定(bind)到各自的函数参数。标准是这样规定的:

§5.2.2 - Function call 5.2.2.4:

. . . The postfix-expression is sequenced before each expression in the expression-list and any default argument. Every value computation and side effect associated with the initialization of a parameter, and the initialization itself, is sequenced before every value computation and side effect associated with the initialization of any subsequent parameter.

但是,the GitHub draft 中缺少其中一个新句子。 :

Every value computation and side effect associated with the initialization of a parameter, and the initialization itself, is sequenced before every value computation and side effect associated with the initialization of any subsequent parameter.

示例那里。它解决了几十年前的问题(as explained by Herb Sutter),具有异常安全性,例如

f(std::unique_ptr<A> a, std::unique_ptr<B> b);

f(get_raw_a(), get_raw_a());

如果其中一个调用 get_raw_a() 会在另一个调用之前抛出,则会泄漏 原始指针与其智能指针参数绑定(bind)。

正如 T.C. 所指出的,该示例存在缺陷,因为从原始指针构造 unique_ptr 是显式的,因此无法编译。*

还要注意这个经典的question (标记为 C,而不是 C++):

int x=0;
x++ + ++x;

仍未定义。

关于c++ - C++17 引入的评估顺序保证是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38501587/

相关文章:

c++ - 将 std::integer_sequence 作为模板参数传递给元函数

c++ - 对象地址是否保证是其类型对齐的倍数?

c++ - 为什么 C++ 不尝试使用第二个模板重载?

c++ - 在 C++17 中没有从 std::string 到 std::string_view 的隐式转换(在 std::experimental::basic_string_view 中)

c++ - 运算符优先级困惑

c++ - 谁能解释为什么 x 显示值 1 而不是 2

c++ - 标识符字符集 (clang)

c++ - 创建显式专用模板类对象会产生 "object has initializer but incomplete type"错误

c++ - 转换为 clang 格式的 C++17 嵌套命名空间?

C# 递增 ToString