Why does an explicit move after use in braced initializer list break the return value?

93 views Asked by At

I had a problem that appeared in GCC and Clang, but not MSVC. The problematic part boils down to this:

#include <utility>
#include <string>
#include <iostream>

auto mkStrings() -> std::pair<std::string, std::string>
{
    std::string r = "r";
    return { r, "s" + std::move(r) }; // !
}

int main()
{
    auto [r, s] = mkStrings();
    std::cout << "'" << r << " " << s << "'" << std::endl;
}

On MSVC, I get:

'r sr'

On GCC 12.2.0 and Clang 15.0.7, it outputs:

' sr'

(On Clang 16.0.1 the compiler segfaults.)

I’m quite convinced the std::move is a problem, but I don’t understand why. I even looked up if I was mistaken that initializer lists are evaluated left-to-right, but according to the answers to this question they are.

Of course, I just removed the std::move, but I’d like to understand why I had to.

2

There are 2 answers

0
Brian Bi On BEST ANSWER

The following constructor is used for the initialization:

template <class U1, class U2>
constexpr pair(U1&& x, U2&& y);

(In C++23, default template arguments will be added, but this doesn't affect the answer.)

Because you're using braced initialization, the parameter x will be initialized before the parameter y. However both x and y must be initialized before the ctor-initializer can be evaluated (which actually initializes the first and second members).

So x is first bound to r, then y to the temporary result of the expression "s" + std::move(r). At this point, r may be moved from.

Afterward, the constructor uses std::forward<U1>(x) to initialize first. But at this point, the move from the object that x refers to has already been performed.

4
273K On

Because the order of the evaluation inside a braced initializer is unspecified. GCC and Clang seem to evaluate "s" + std::move(r) first, after that r is empty.

Order of evaluation

21) Every expression in a comma-separated list of expressions in a parenthesized initializer is evaluated as if for a function call (indeterminately-sequenced)