调用运算符参数评估顺序

call operator argument evaluation order

#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++17 之前,修改相同 l-value 的链式函数调用确实是未定义的行为,因为这些表达式的求值顺序未指定。

但是,一个 proposal 修复已合并到 c++17 中。

这是相关的 rule(强调我的),其中还包含提案中的示例,说明其工作原理:

The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter. [Note: All side effects of argument evaluations are sequenced before the function is entered (see [intro.execution]). — end note] [Example:

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
}

— end example]


虽然上述规则仅严格引用 built-in operator(),并且您有 user-defined 个运算符,但适用相同的求值顺序规则,因此 rule:

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator. ... However, the operands are sequenced in the order prescribed for the built-in operator.