Home > Software design >  Copy elision and move constructor
Copy elision and move constructor

Time:11-28

Consider the following definition of Person:

struct Person
{
    Person() { std::cout << "construct, "; }
    Person(const Person&) { std::cout << "copy\n"; }
    Person(Person&&) { std::cout << "move\n"; }
};

And 3 different functions to create a Person:

Person create1()
{
    std::cout << "create1: ";
    Person p1{};
    return p1;
}

Person create2()
{
    std::cout << "create2: ";
    if constexpr (true)
    {
        Person p1{};
        return p1;
    }
    else
    {
        Person p2{};
        return p2;
    }
}
    
Person create3()
{
    std::cout << "create3: ";
    if constexpr (true)
    {
        return Person{};
    }
    else
    {
        return Person{};
    }
}

Finally, I call the create functions as follows:

int main()
{
    Person p1 = create1();
    Person p2 = create2();
    Person p3 = create3();
    return 0;
}

The output is:

create1: construct
create2: construct, move
create3: construct

What bothers me is the output of create2. If in create1 and create3, the move constructor is not called, why is it called in create2?

I am using GCC 12.0.0.

EDIT: Clang 13.0.0 does not call the move constructor.

CodePudding user response:

I think it is an instance of NRVO in constant expression and cppreference says:

In constant expression and constant initialization, return value optimization (RVO) is guaranteed, however, named return value optimization (NRVO) is forbidden:

This is related to CWG 2278.

EDIT

I reconsidered :-) It is called from main(), so it can't be constant initialization. With actual constant initialization neither gcc nor clang do NRVO. In this example (see godbolt):

struct Person {
    bool was_moved;
    constexpr Person() : was_moved{false} { }
    constexpr Person(const Person&) : was_moved{false} { }
    constexpr Person(Person&&) : was_moved{true} { }
};
constexpr Person create() {
    if (true) {
        Person p1;
        return p1;
    }
    else {
        Person p2;
        return p2;
    }
}
constexpr Person p = create();

int main() {
    return p.was_moved;
}

p.was_moved is 1.

As to why gcc doesn't do NRVO in create2(), one answer could be that it is not mandatory, as per the cppreference link earlier. Another could be that gcc implementation of NRVO is very "fragile". For example it works when only one named variable is involved:

Person create4() {
    Person p{};
    if  (true) {
        return p;
    }
    else {
        return p;
    }
}

but stops working when this whole thing is put in a sub-scope:

Person create5() {
  {
    Person p{};
    if  (true) {
        return p;
    }
    else {
        return p;
    }
  }
}

(see godbolt)

There is gcc bug from 2012 regarding both those issues: Bug 53637 - NRVO not applied where there are two different variables involved .

  • Related