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.
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.