Does the behavior of guaranteed copy elision depend on existence of user-defined copy constructor?
Asked Answered
P

1

18

The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:

#include <cassert>

struct S {
    int i;
    int *p;
    S() : i(0), p(&i) {}
    // S(const S &s) : i(s.i), p(&i) {}  // #1
    // S(const S &s) : i(s.i), p(s.p) {} // #2
    // S(const S &s) = delete;           // #3
};

S make_S() {return S{};}

int main()
{
    S s = make_S();
    assert(s.p == &s.i);
}

With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.

However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?

Poniard answered 20/2, 2018 at 6:34 Comment(7)
Guaranteed copy elision and RVO are not one and the same, thoughBarbiebarbieri
Does your compiler fully support that feature?Mitchellmitchem
Also worth mentioning it fails on Clang 7.0.0 as wellBarbiebarbieri
@StoryTeller RVO in this example meets the criteria of guaranteed copy elision. It is an initialization from a prvalue of the same class type.Poniard
@Mitchellmitchem It seems GCC fully supports guaranteed copy elision since version 7 from this tablePoniard
I'm probably mistaken, but I really can't find anything wrong with your example that would make the copy elision faulty. I suspect a QOI issue (for both Clang and GCC).Barbiebarbieri
Just for clarification, there is no RVO in C++17, since no temporary is materialized when a prvalue is returned. There are no constructors to be elided then.Hautemarne
H
17

Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):

When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]

In your case, when I made both copy and move constructors defaulted:

S(const S &) = default;
S(S &&) = default;

assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.

Hautemarne answered 20/2, 2018 at 9:2 Comment(2)
And what do you know, bloating the object size for the OP's code, suddenly makes it all tick. I guess because it doesn't fit in a register anymore.Barbiebarbieri
@StoryTeller Exactly, it just came to my mind to do the same experiment. But you were faster :)Hautemarne

© 2022 - 2024 — McMap. All rights reserved.