Skip to content

Commit bc713f6

Browse files
committed
PR48763: Better handling for classes that inherit a default constructor.
The C++ standard wording doesn't appear to properly handle the case where a class inherits a default constructor from a base class. Various properties of classes are defined in terms of the corresponding property of the default constructor, and in this case, the class does not have a default constructor despite being default-constructible, which the wording doesn't handle properly. This change implements a tentative fix for these problems, which has also been proposed to the C++ committee: if a class would inherit a default constructor, and does not explicitly declare one, then one is implicitly declared.
1 parent c535a7f commit bc713f6

File tree

7 files changed

+64
-18
lines changed

7 files changed

+64
-18
lines changed

clang/include/clang/AST/CXXRecordDeclDefinitionBits.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ FIELD(HasUninitializedFields, 1, NO_MERGE)
131131
/// constructors from a base class.
132132
FIELD(HasInheritedConstructor, 1, NO_MERGE)
133133

134+
/// True if there are any member using-declarations that inherit
135+
/// default constructors from a base class.
136+
FIELD(HasInheritedDefaultConstructor, 1, NO_MERGE)
137+
134138
/// True if there are any member using-declarations named
135139
/// 'operator='.
136140
FIELD(HasInheritedAssignment, 1, NO_MERGE)

clang/include/clang/AST/DeclCXX.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -744,9 +744,14 @@ class CXXRecordDecl : public RecordDecl {
744744
///
745745
/// This value is used for lazy creation of default constructors.
746746
bool needsImplicitDefaultConstructor() const {
747-
return !data().UserDeclaredConstructor &&
748-
!(data().DeclaredSpecialMembers & SMF_DefaultConstructor) &&
749-
(!isLambda() || lambdaIsDefaultConstructibleAndAssignable());
747+
return (!data().UserDeclaredConstructor &&
748+
!(data().DeclaredSpecialMembers & SMF_DefaultConstructor) &&
749+
(!isLambda() || lambdaIsDefaultConstructibleAndAssignable())) ||
750+
// FIXME: Proposed fix to core wording issue: if a class inherits
751+
// a default constructor and doesn't explicitly declare one, one
752+
// is declared implicitly.
753+
(data().HasInheritedDefaultConstructor &&
754+
!(data().DeclaredSpecialMembers & SMF_DefaultConstructor));
750755
}
751756

752757
/// Determine whether this class has any user-declared constructors.

clang/lib/AST/DeclCXX.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
8181
HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false),
8282
HasOnlyCMembers(true), HasInClassInitializer(false),
8383
HasUninitializedReferenceMember(false), HasUninitializedFields(false),
84-
HasInheritedConstructor(false), HasInheritedAssignment(false),
84+
HasInheritedConstructor(false),
85+
HasInheritedDefaultConstructor(false),
86+
HasInheritedAssignment(false),
8587
NeedOverloadResolutionForCopyConstructor(false),
8688
NeedOverloadResolutionForMoveConstructor(false),
8789
NeedOverloadResolutionForCopyAssignment(false),
@@ -814,6 +816,8 @@ void CXXRecordDecl::addedMember(Decl *D) {
814816
// constructor [...]
815817
if (Constructor->isConstexpr() && !Constructor->isCopyOrMoveConstructor())
816818
data().HasConstexprNonCopyMoveConstructor = true;
819+
if (!isa<CXXConstructorDecl>(D) && Constructor->isDefaultConstructor())
820+
data().HasInheritedDefaultConstructor = true;
817821
}
818822

819823
// Handle destructors.

clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p15.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace default_ctor {
3636
};
3737

3838
struct A {
39-
A(); // expected-note {{candidate}}
39+
A();
4040

4141
A(C &&);
4242
C &operator=(C&&);
@@ -48,7 +48,7 @@ namespace default_ctor {
4848
};
4949

5050
struct B {
51-
B(); // expected-note {{candidate}}
51+
B();
5252

5353
B(C &&);
5454
C &operator=(C&&);
@@ -66,9 +66,9 @@ namespace default_ctor {
6666
using B::operator=;
6767
};
6868
struct D : A, B {
69-
using A::A; // expected-note 2{{inherited here}}
69+
using A::A; // expected-note {{inherited here}}
7070
using A::operator=;
71-
using B::B; // expected-note 2{{inherited here}}
71+
using B::B; // expected-note {{inherited here}}
7272
using B::operator=;
7373

7474
D(int);
@@ -84,7 +84,9 @@ namespace default_ctor {
8484

8585
// D does not declare D(), D(D&&), nor operator=(D&&), so the base class
8686
// versions are inherited.
87-
D d; // expected-error {{ambiguous}}
87+
// Exception: we implicitly declare a default constructor so that we can
88+
// reason about its properties.
89+
D d; // ok, implicitly declared
8890
void f(D d) {
8991
D d2(static_cast<D&&>(d)); // ok, ignores inherited constructors
9092
D d3(convert_to_D1{}); // ok, ignores inherited constructors

clang/test/CXX/special/class.ctor/p6-0x.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,34 @@ namespace UnionCtors {
9494
friend constexpr E::E() noexcept; // expected-error {{follows non-constexpr declaration}}
9595
};
9696
}
97+
98+
namespace PR48763 {
99+
// FIXME: We implement a speculative wording fix here: if a class inherits a
100+
// default constructor and doesn't declare one itself, we declare an default
101+
// constructor implicitly. This allows us to meanignfully reason about
102+
// whether that default constructor is constexpr, trivial, and so on.
103+
struct A { constexpr A() {} };
104+
struct B : A {
105+
using A::A;
106+
constexpr B(int) {}
107+
};
108+
struct C { B b; };
109+
constexpr C c;
110+
111+
struct D { int n; };
112+
struct E : D { using D::D; E(int); };
113+
static_assert(E().n == 0, "");
114+
static_assert(E{}.n == 0, "");
115+
116+
struct F { E e; };
117+
static_assert(F().e.n == 0, "");
118+
static_assert(F{}.e.n == 0, "");
119+
120+
union U { E e; };
121+
U u; // OK, trivial default constructor
122+
123+
struct G { G(); };
124+
struct H : D { using D::D; H(int); G g; };
125+
union V { H h; }; // expected-note {{field 'h' has a non-trivial default constructor}}
126+
V v; // expected-error {{deleted}}
127+
}

clang/test/CXX/special/class.inhctor/p1.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,25 @@
44
// for the wording that used to be there.
55

66
struct A {
7-
A(...); // expected-note {{candidate constructor}} expected-note {{candidate inherited constructor}}
8-
A(int = 0, int = 0, int = 0, int = 0, ...); // expected-note 3{{candidate constructor}} expected-note 3{{candidate inherited constructor}}
9-
A(int = 0, int = 0, ...); // expected-note 3{{candidate constructor}} expected-note 3{{candidate inherited constructor}}
7+
A(...); // expected-note {{candidate constructor}}
8+
A(int = 0, int = 0, int = 0, int = 0, ...); // expected-note 3{{candidate constructor}} expected-note 2{{candidate inherited constructor}}
9+
A(int = 0, int = 0, ...); // expected-note 3{{candidate constructor}} expected-note 2{{candidate inherited constructor}}
1010

1111
template<typename T> A(T, int = 0, ...);
1212

1313
template<typename T, int N> A(const T (&)[N]); // expected-note {{candidate constructor}} expected-note {{candidate inherited constructor}}
1414
template<typename T, int N> A(const T (&)[N], int = 0); // expected-note {{candidate constructor}} expected-note {{candidate inherited constructor}}
1515
};
1616

17-
struct B : A {
18-
using A::A; // expected-note 9{{inherited here}}
17+
struct B : A { // expected-note {{because base class 'A' has multiple default constructors}}
18+
using A::A; // expected-note 6{{inherited here}}
1919
B(void*);
2020
};
2121

2222
struct C {} c;
2323

2424
A a0{}; // expected-error {{ambiguous}}
25-
B b0{}; // expected-error {{ambiguous}}
25+
B b0{}; // expected-error {{deleted}}
2626

2727
A a1{1}; // expected-error {{ambiguous}}
2828
B b1{1}; // expected-error {{ambiguous}}

clang/test/CXX/special/class.inhctor/p2.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,16 @@ static_assert(ce.k2 == 'x', "");
9696

9797

9898
struct TemplateCtors { // expected-note 2{{candidate constructor (the implicit}}
99-
constexpr TemplateCtors() {} // expected-note {{candidate inherited constructor}}
99+
constexpr TemplateCtors() {}
100100
template<template<int> class T> TemplateCtors(X<0>, T<0>); // expected-note {{here}} expected-note {{candidate inherited constructor}}
101101
template<int N> TemplateCtors(X<1>, X<N>); // expected-note {{here}} expected-note {{candidate inherited constructor}}
102102
template<typename T> TemplateCtors(X<2>, T); // expected-note {{here}} expected-note {{candidate inherited constructor}}
103103

104104
template<typename T = int> TemplateCtors(int, int = 0, int = 0);
105105
};
106106

107-
struct UsingTemplateCtors : TemplateCtors { // expected-note 2{{candidate constructor (the implicit}}
108-
using TemplateCtors::TemplateCtors; // expected-note 6{{inherited here}}
107+
struct UsingTemplateCtors : TemplateCtors { // expected-note 3{{candidate constructor (the implicit}}
108+
using TemplateCtors::TemplateCtors; // expected-note 5{{inherited here}}
109109

110110
constexpr UsingTemplateCtors(X<0>, X<0>) {} // expected-note {{not viable}}
111111
constexpr UsingTemplateCtors(X<1>, X<1>) {} // expected-note {{not viable}}

0 commit comments

Comments
 (0)