Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy elision for direct base class initialization? [duplicate]

The following code fails to compile with both Gcc and Clang because of the copy construction of B base class suboject inside A constructor:

struct B{
  B();
  B(const B&) =delete;
  };

struct A:B{
  A():B(B()){} //=> error: use of deleted function...
  };

Nevertheless according to [class.base.init]/7:

The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of [dcl.init] for direct-initialization.

So the initialization rule is the same for members or direct bases. For a member subobject, Gcc and Clang do not use the deleted copy constructor:

struct A2:B{
  B b;
  A2():b(B()){} //=> OK the result object of B() is b
  };

Is not it a compiler bug of both Clang and Gcc? Should not the copy constructor of B be elided in A()?


Interestingly, even if gcc checks if a copy construction is well formed, it elides this copy constructor call, see assembly here

like image 579
Oliv Avatar asked Dec 21 '18 11:12

Oliv


People also ask

Under what conditions copy elision is performed?

In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement ...

How do you avoid copy elision?

GCC provides the -fno-elide-constructors option to disable copy-elision. This option is useful to observe (or not observe) the effects of return value optimization or other optimizations where copies are elided. It is generally not recommended to disable this important optimization.

What is meant by copy initialization?

The Copy initialization is basically an overloaded constructor. Direct initialization can be done using assignment operator. This initializes the new object with an already existing object. This assigns the value of one object to another object both of which are already exists.

Is NRVO guaranteed?

Compilers often perform Named Return Value Optimization (NRVO) in such cases, but it is not guaranteed.


1 Answers

This is indeed very strange. First of all, independent of whether the copy constructor should be called here or not, since [class.base.init]/7 does not distinguish between initializing a base and initializing a member, behavior should the same in both cases. I can't find any additional wording in the standard that would somehow introduce an asymmetry between initialization of a base vs initialization of a member. Based on this alone, I would say that we can conclude that there's a compiler bug here one way or the other. icc and MSVC seem to at least behave consistently when it comes to initializing a base vs initializing a member. However, icc and MSVC disagree about whether the code should be accepted or not.

I believe an answer to the question of whether the copy constructor should be called or not can be found if we look at [class.base.init]/7 again:

The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of [dcl.init] for direct-initialization. […]

emphasis mine. I believe the relvant bit of [dcl.init] should be [dcl.init]/17.6, where we find:

  • If the destination type is a (possibly cv-qualified) class type:

    • If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. […]

    • Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). […]

[…]

If 17.6.2 is to apply, that would mean that the copy-constructor should be called, which would make MSVC the only major compiler that behaves correctly in this example. However, my interpretation would be that 17.6.1 applies in general and icc is correct, i.e., your code should compile. Which means what you have here is potentially even two bugs in GCC and clang (initialization of base vs. initialization of member behaves differently + mem-initializer invokes copy-ctor although it shouldn't), and one in MSVC…

like image 94
Michael Kenzel Avatar answered Oct 23 '22 09:10

Michael Kenzel