-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Full name of submitter: Svetlin Totev
When is the implicit default constructor created:
11.4.5.2 Default constructors [class.default.ctor]
- ... 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.