Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does C++ not know to do an implicit move in the return when the variable is used in an initializer list?

Consider this code:

#include <iostream>
template<typename A>
struct S{
    S(const A& a){
        std::cout << "L\n";
    }
    S(A&& a){
        std::cout << "R\n";
    }
};
S<int> f1(){
    int a = 1;
    return {a};
}
S<int> f2(){
    int a = 1;
    return a;
}
S<int> f3(){
    int a = 1;
    return {std::move(a)};
}
int main()
{
    f1();
    f2();
    f3();
}

Output is

L
R
R

As you may know C++ implicitly moves in the return (in f2). When we do it manually in the initializer list it works (f3), but it is not done automagically by C++ in f1.

Is there a good reason why this does not work, or is it just a corner case deemed not important enough to be specified by the standard?

P.S. I know compilers can (sometimes must) do RVO, but I do not see how this could explain the output.

like image 670
NoSenseEtAl Avatar asked Mar 19 '21 16:03

NoSenseEtAl


1 Answers

The nice thing about:

return name;

Is that it's a simple case to reason about: you're obviously returning just an object, by name, there's no other shenanigans going on here at all. And yet, this specific case, has led to patch after patch after patch after patch. So, maybe not so simple after all.

Once we throw in any further complexity on top of that, it gets way more complicated.

With returning an initializer list, we would have to start considering all sorts of other cases:

// obviously can't move
return {name, name};       

// 'name' might refer to an automatic storage variable, but
// what if 'other_name' is an alias to it? What if 'other_name'
// is a separate automatic storage variable but is somehow
// dependent on 'name' in a way that matters?
return {name, other_name}; 

You just... can't know. The only case that we could definitely consider is an initializer list consisting of a single name:

return {name};

That case is probably fine to implicitly move from. But the thing is, in that case, you can just move:

return {std::move(name)};

The problem specifically with the return name; case is that return std::move(name); was sometimes mandatory and sometimes a pessimization and we would like to get the point where you always just write the one thing and get the optimal behavior. There's no such concern here, return {std::move(name)}; can't inhibit copy elision in the same way. So it's just less of an issue to have to write that.

like image 176
Barry Avatar answered Nov 16 '22 11:11

Barry