16

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

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • Nice Q... are you vexed with whitespace though? – YSC Dec 21 '18 at 11:14
  • 1
    @YSC [Whitespace](https://en.wikipedia.org/wiki/Whitespace_%28programming_language%29) is not my preferred programming language any more! – Oliv Dec 21 '18 at 11:20
  • I parse "delegating constructor" to mean, well, a delegating constructor. As in, delegating to another constructor of the same class, and not a constructor of the base class. So I don't see how the quoted clause applies to base classes. – Sam Varshavchik Dec 21 '18 at 11:45
  • 1
    @SamVarshavchik You should look for the definition of suboject! – Oliv Dec 21 '18 at 12:12
  • @xskxzr Indeed that is answering my question. – Oliv Dec 21 '18 at 14:36

1 Answers1

3

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…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • Here's a guess: since `B`'s constructor is used to initialize `A`, isn't the "source type" `B` and the "destination" `A`, which would make "the source type is the same class as the class of the destination" false? – Nelfeal Dec 21 '18 at 12:11
  • 1
    @Nelfeal I don't think so. The object to be be initialized is the `B` base-class subobject within the `A` object… – Michael Kenzel Dec 21 '18 at 12:17
  • 1
    The first paragraph *if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of destination* applies here. The initializer expression: `B()` is a prvalue of type B, and the destination type is B. – Oliv Dec 21 '18 at 12:17
  • I would also think so. In this case, I guess you found a bug in GCC, clang, and MSVC… – Michael Kenzel Dec 21 '18 at 12:18
  • @Oliv: but [class.base.init]/7 says that "according to the initialization rules of [dcl.init] for direct-initialization.". So if we look at this rule, then we shouldn't consider the first bullet point, as it is not about direct initialization. – geza Dec 21 '18 at 12:45
  • @geza The first bullet bellow *if the destination type is a (possibly cv-qualified) class-type)* does not seems to be restricted to copy-initialization?? (Maybe??? I don't know) The second bullet "*Otherwise , if the initialization is direct-initialization...*, the "if..." preposition is here to restrict to direct-initialization: it means if it is a copy-initialization, jump to the next bullet which begins with *Otherwise (i.e., for the remaining copy-initialization cases),* – Oliv Dec 21 '18 at 13:26
  • @Oliv: yes, maybe you're right, the first bullet point still can be about direct initialization. Another blurry thing is the type. `B` is a subobject of `A`, so I'm not sure that `B` is the destination type in this case. And there can be implementation problems: if `B` is initialized by a function call `getB()`, then `getB()` will create a `B`. But, here, an `A` need to be created. – geza Dec 21 '18 at 14:31
  • @geza the quote from [class.base.init]/7 given in the original post literally says *"The expression-list or braced-init-list in a mem-initializer is used to initialize the designated **subobject** […]"*. The destination type of an initialization is the type of the object that that particular initialization is supposed to initialize. I don't see any way in which this could possibly be interpreted such that there would be any doubt about the fact that the initialization concerns the base class subobject and the destination type is the type of that particular base class… – Michael Kenzel Dec 21 '18 at 14:58
  • Yes, I think you're right. But still, looking at this problem from the compiler-implementation viewpoint, it would be hard to do. So in my opinion, the standard should be modified to implicitly say that in this case, copy elision won't be applied (I mean that the temporary will be materialized). – geza Dec 21 '18 at 15:07