Skip to content

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

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.

Activity

  1. jensmaurer commented on Jun 3, 2024

    @jensmaurer
    Member

    CWG2273 touches the same subject.

  2. jensmaurer commented on Jun 4, 2024

    @jensmaurer
    Member

    Since P0136, it is rather clear that inherited constructors are not redeclared in the derived class, but instead are simply found by lookup. Thus, they are not considered "user-declared". If they were, the multiple-inheritance case would need special treatment (see CWG2799).

    The situation might be considered undesirable; please write a short paper to WG21 (which will be reviewed by EWG) to suggest a change here.

  3. svetli97 commented on Jun 4, 2024

    @svetli97
    Author

    I'm certainly not wasting any more time with this. The sunk cost fallacy has kept dragging me back to this language for too long. One inconsistent behaviour can't be fixed because it would break another inconsistent behaviour. I guess that's just how this language will always be. And it will always keep getting worse. I give up.

  4. jensmaurer commented on Jun 4, 2024

    @jensmaurer
    Member

    Turns out this is exactly CWG2799.

  5. changed the title [-][class.default.ctor] Default constructor implicitly declared for class with inherited constructors[/-] [+]CWG2799 [class.default.ctor] Default constructor implicitly declared for class with inherited constructors[/+] on Jun 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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