Confusing behaviour around mandatory elision of copy/move

Viewed 55

While toying with RVO I encountered the following issue and I can't get my head around it.

#include <iostream>
struct A {
    A* p = this;
};

A func()
{
    return A();
}

int main()
{
    A a = A();
    std::cout << "address of a: " << &a << " - a.p = " << a.p << std::endl;

    A b = func();          
    std::cout << "address of b: " << &b << " - b.p = " << b.p << std::endl;
}

Result:

address of a: 0x7ffe5b11b7a0 - a.p = 0x7ffe5b11b7a0

address of b: 0x7ffe5b11b790 - b.p = 0x7ffe5b11b770

All fine with a. However note that b.p doesn't actually point to b as if it was pointing to a different object.

I'm using gcc 8.3.0 which supports C++17. My understanding is that from C++17 onwards, RVO is mandatory and initialisation from a prvalue (i.e. A b = func();) should also trigger mandatory elision. Therefore, in theory, only one constructor should be called to initialise b so p should not be able to point to anything else than b. Could someone explain me what is happening here?

Note: initialising p in a default constructor instead of using default member initialisation doesn't change the behaviour.

I toyed with this a bit more and realised that defining a default constructor AND any one of the following special functions makes p point to b as expected:

  • destructor
  • move constructor
  • move assignment operator
  • copy constructor

Instead of helping me understand the problem, this confused me even more...

As a side note, I have witnessed the exact same behaviour with gcc 11.0.0 and clang 11.0.0 (using Wandbox) so this is unlikely a bug.

0 Answers
Related