Skip to content

CWG2799 [class.default.ctor] Default constructor implicitly declared for class with inherited constructors #545

@svetli97

Description

@svetli97

Full name of submitter: Svetlin Totev

When is the implicit default constructor created:
11.4.5.2 Default constructors [class.default.ctor]

  1. ... If there is no user-declared constructor for class X, a non-explicit
    constructor having no parameters is implicitly declared as defaulted
    ([dcl.fct.def])...

What inheriting constructors does:
9.9 The using declaration [namespace.udecl]

13 Constructors that are named by a using-declaration are treated as though
they were constructors of the derived class when looking up the
constructors of the derived class ([class.qual]) or forming a set of
overload candidates ([over.match.ctor], [over.match.copy],
[over.match.list]).

Which constructor is called when passed {}:
12.2.2.8 Initialization by list-initialization [over.match.list]

(1.1) If the initializer list is not empty or T has no default constructor,
overload resolution is first performed where the candidate functions are
the initializer-list constructors ([dcl.init.list]) of the class T and the
argument list consists of the initializer list as a single argument.

Issue description:
Constructors of a derived class inherited from the base class with a using-declaration are not accounted for when checking if there are user-declared constructors. So a default constructor is implicitly declared. Even though the user explicitly declared constructors inherited from the base class.

This is an issue when the derived class must not have a default constructor. Which is likely what any user assumes would happen when inheriting other constructors. The default constructor being added to the constructor lookup is also a big problem when inheriting initializer_list constructors as the default one has higher priority when calling the constructor with {}.

Both gcc and clang implement this questionable behaviour.

Explicitly declaring the default constructor as deleted doesn't help as this still counts as a declaration of a default constructor. The only difference being the compilation error you get.

Example:

#include<initializer_list>
struct A { A(std::initializer_list<int> il) {}; };
struct B : A { using A::A; };
// workaround 1: manually doing what "using A::A;" should do
struct C : A { C(std::initializer_list<int> il) : A(il) {}; };
// workaround 2: declaring a useless constructor to avoid the implicit default constructor
struct D : A {
    using A::A;
    private:
    D(void*); // the argument should probably be something that can't be constructed
};

int main()
{
    // A(); // doesn't work, as expected
    A{}; // works as expected

    A a1 {};        // ok
    A a2 {1, 2, 3}; // ok
    A a3 {5};       // ok

    B b1 {};        // calls implicit B() which calls A() which doesn't exist -> fail
    B b2 {1, 2, 3}; // ok
    B b3 {5};       // ok

    C c1 {};        // ok
    C c2 {1, 2, 3}; // ok
    C c3 {5};       // ok

    D d1 {};        // ok
    D d2 {1, 2, 3}; // ok
    D d3 {5};       // ok
    return 0;
}

Suggested resolution:
The using-declaration inherited constructors should count as user-declared constructors as it is an explicit declaration of constructors made by the user. So the default constructor shouldn't be implicitly declared.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions