24

I am trying to use a combination of the C++ 11 direct data member initialisation and the "using" syntax to inherit the constructors of a base class. Now with gcc 5.4.0 (on Ubuntu 16.04) I observe a strange error, if the data member type has no default constructor. It is probably easiest to understand when looking on the following minimal example:

#include <iostream>

struct Foo {
  Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
};

struct Base {
  Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; }
};

struct Derived : public Base {
  using Base::Base;
  Foo foo{42};
};

int main() {
  Derived derived{120};
}

This code compiles and executes with the expected behaviour with clang. With gcc it does not compile, because the compiler deletes the constructor Derived::Derived(int):

ttt.cpp: In function ‘int main()’:
ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)’
   Derived derived{120};
                      ^
ttt.cpp:12:15: note: ‘Derived::Derived(int)’ is implicitly deleted because the default definition would be ill-formed:
   using Base::Base;
               ^
ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()’
ttt.cpp:4:3: note: candidate: Foo::Foo(int)
   Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
   ^
ttt.cpp:4:3: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&)
 struct Foo {
        ^
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&)
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided

If I add a default constructor to Foo like this:

  Foo() { std::cout << "Foo::Foo()" << std::endl; };

also gcc can compile it. The code behaves exactly in the same way, in particular the added default constructor of Foo never gets executed.

So my question is now, is this valid C++ 11? If yes, I probably have found a bug in gcc. Otherwise, shouldn't both gcc and clang give me an error message that this is not valid C++ 11?

Edit after the question was nicely answered by @vlad-from-moscow: This bug seems to be present also in gcc 6.2, so I will file a bug report.

2nd edit: There already is a bug, which I didn't find in the first search: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

  • I cannot find anything about this on pages like cppreference.com. Both the "using" syntax and the brace initialiser are described there, but it is not mentioned anything about combining both. – Martin Hierholzer Dec 02 '16 at 13:03

2 Answers2

11

The gcc does not satisfy the C++ Standard. The inherited constructor of the class Derived should call the Base constructor in its mem-initializer list with the argument specified for the Derived inherited constructor.

There is written in the C++ Standard (12.9 Inheriting constructor)

8 An inheriting constructor for a class is implicitly defined when it is odr-used (3.2) to create an object of its class type (1.8). An implicitly-defined inheriting constructor performs the set of initializations of the class that would be performed by a user-written inline constructor for that class with a mem-initializer-list whose only mem-initializer has a mem-initializer-id that names the base class denoted in the nested-name-specifier of the using-declaration and an expression-list as specified below, and where the compound-statement in its function body is empty (12.6.2). If that user-written constructor would be ill-formed, the program is ill-formed. Each expression in the expression-list is of the form static_cast(p), where p is the name of the corresponding constructor parameter and T is the declared type of p.

Also according to the section (12.6.2 Initializing bases and members)

8 In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has noctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then

— if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • @VladfromMoscow: The issue is not so much that `Derived(120)` does not call `Base(120)` and more that it mistakenly assumes the expression-list will be `foo()` (which is invalid) even though an initializer is provided and the actual call will be `foo(42)`. If you remove the `foo` data member, then gcc accepts the code. – Matthieu M. Dec 02 '16 at 15:49
  • @MatthieuM. I have appended my answer. However I found that there are contradictions between various editions of the Standard. – Vlad from Moscow Dec 02 '16 at 16:21
5

It looks like you're right, there's a bug in gcc

From §12.9 [class.inhctor]:

A using-declaration (7.3.3) that names a constructor implicitly declares a set of inheriting constructors. The candidate set of inherited constructors from the class X named in the using-declaration consists of actual constructors and notional constructors that result from the transformation of defaulted parameters as follows:

  • all non-template constructors of X

So this means that your Derived class should definnitely get a constructor from its base that accepts an int. Following the normal rules of in-class member initialization, constructing an instance of Derived shouldn't be a problem without a default constructor for Foo because it's not being used. Hence, there's a bug in gcc:

§13.3.1.3 Initialization by constructor [over.match.ctor]

When objects of class type are direct-initialized (8.5) [...], overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized.

So the constructor Foo::Foo(int) should have been selected, which it clearly wasn't in gcc.


One question I had after reading this was "Does this cause the default constructor for Derived to be deleted?" The answer is no.

Conveniently, the standard provides an example below this excerpt (I'm excising what's not needed):

struct B1 {
   B1(int);
};

struct D1 : B1 {
   using B1::B1;
};

The set of constructors present in D1 is [Emphasis mine]

  • D1(), implicitly-declared default constructor, ill-formed if odr-used
  • D1(const D1&), implicitly-declared copy constructor, not inherited
  • D1(D1&&), implicitly-declared move constructor, not inherited
  • D1(int), implicitly-declared inheriting constructor
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • 2
    This has nothing to do with the default constructor of Derived or Base. gcc requires a default constructor of Foo, but it never actually calls it. – Martin Hierholzer Dec 02 '16 at 13:21
  • @MartinHierholzer: You said "This code compiles and executes with the expected behaviour with clang. With gcc it does not compile, because the compiler deletes the constructor of Derived:". This is incorrect gcc behavior, and what I'm addressing here. You shouldn't need to do anything more. – AndyG Dec 02 '16 at 13:28
  • 1
    Referring to your edit: This is still not about whether the default constructor of Derived gets deleted or not. I am not interested in the default constructor of Derived, it is never used and gcc never complained about not having it. Instead, gcc deletes the wanted constructor Derived::Derived(int), because the default constructor of Foo does not exist! – Martin Hierholzer Dec 02 '16 at 13:29
  • @MartinHierholzer: I've made more edits for clarity. I don't think I can make a stronger argument than this. – AndyG Dec 02 '16 at 13:46