首页 > 解决方案 > 调用运算符参数评估顺序

问题描述

#include <string>

struct X
{
    char y;
    std::string z;

    X & operator()(std::string && s)
    {
        z = std::move(s);
        return *this;
    }

    X & operator()(char c)
    {
        y = c;
        return *this;
    }
};

int main()
{
    X x;
    std::string y("abc");

    x(y[0])(std::move(y));
}

main未定义行为的最后一行吗?我猜是的,因为它会展开如下,但只是想确保对调用运算符或成员函数调用没有更严格的保证

X::operator()(&X::operator()(&x, y[0]), std::move(z))

请添加来自标准或 cppref 的引用

标签: c++language-lawyer

解决方案


在 c++17 之前,链接修改相同左值的函数调用,就像在你的例子中一样,确实是未定义的行为,因为这些表达式的评估顺序是未指定的。

但是,修复该问题的提案已合并到 c++17 中。

这是相关规则(重点是我的),其中还包含提案中的一个示例,该示例显示了它是如何工作的:

后缀表达式在表达式列表中的每个表达式和任何默认参数之前排序。参数的初始化,包括每个相关的值计算和副作用,相对于任何其他参数的初始化顺序是不确定的。[注意:参数评估的所有副作用在输入函数之前进行排序(参见 [intro.execution])。— 尾注] [示例:

void f() {
 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, "");
 assert(s == "I have heard it works only if you believe in it");       // OK
}

——结束示例]


虽然上述规则仅严格引用内置的operator(),并且您有用户定义的运算符,但由于此规则,有关评估顺序的相同规则适用:

如果任一操作数的类型是类或枚举,则可能会声明实现此运算符的用户定义的运算符函数,或者可能需要用户定义的转换将操作数转换为适合于内置的类型。在运算符中。...但是,操作数按照内置 operator 规定的顺序进行排序


推荐阅读