cplusplus / CWG

Core Working Group
23 stars 7 forks source link

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

Open svetli97 opened 1 month ago

svetli97 commented 1 month ago

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.

jensmaurer commented 1 month ago

CWG2273 touches the same subject.

jensmaurer commented 1 month ago

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.

svetli97 commented 1 month ago

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.

jensmaurer commented 1 month ago

Turns out this is exactly CWG2799.