Skip to content

Commit 5105ecc

Browse files
offsetofcor3ntin
andauthored
[clang] Refine handling of C++20 aggregate initialization (#131320)
* Move parts of `InitializationSequence::InitializeFrom` corresponding to C++ [dcl.init.general] p16.6.1 and p16.6.2 into a separate function,`TryConstructorOrParenListInitialization` * Use it in `TryListInitialization` to implement [dcl.init.list] p3.2 * Fix parenthesized aggregate initialization being attempted in copy-initialization contexts or when the constructor call is ambiguous Co-authored-by: cor3ntin <[email protected]>
1 parent db33978 commit 5105ecc

File tree

3 files changed

+183
-56
lines changed

3 files changed

+183
-56
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ Bug Fixes to C++ Support
353353
- Fixed an assertion failure affecting code that uses C++23 "deducing this". (#GH130272)
354354
- Clang now properly instantiates destructors for initialized members within non-delegating constructors. (#GH93251)
355355
- Correctly diagnoses if unresolved using declarations shadows template paramters (#GH129411)
356+
- Fixed C++20 aggregate initialization rules being incorrectly applied in certain contexts. (#GH131320)
356357
- Clang was previously coalescing volatile writes to members of volatile base class subobjects.
357358
The issue has been addressed by propagating qualifiers during derived-to-base conversions in the AST. (#GH127824)
358359
- Fixed a Clang regression in C++20 mode where unresolved dependent call expressions were created inside non-dependent contexts (#GH122892)

clang/lib/Sema/SemaInit.cpp

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4634,6 +4634,59 @@ static void TryConstructorInitialization(Sema &S,
46344634
IsListInit | IsInitListCopy, AsInitializerList);
46354635
}
46364636

4637+
static void TryOrBuildParenListInitialization(
4638+
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
4639+
ArrayRef<Expr *> Args, InitializationSequence &Sequence, bool VerifyOnly,
4640+
ExprResult *Result = nullptr);
4641+
4642+
/// Attempt to initialize an object of a class type either by
4643+
/// direct-initialization, or by copy-initialization from an
4644+
/// expression of the same or derived class type. This corresponds
4645+
/// to the first two sub-bullets of C++2c [dcl.init.general] p16.6.
4646+
///
4647+
/// \param IsAggrListInit Is this non-list-initialization being done as
4648+
/// part of a list-initialization of an aggregate
4649+
/// from a single expression of the same or
4650+
/// derived class type (C++2c [dcl.init.list] p3.2)?
4651+
static void TryConstructorOrParenListInitialization(
4652+
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
4653+
MultiExprArg Args, QualType DestType, InitializationSequence &Sequence,
4654+
bool IsAggrListInit) {
4655+
// C++2c [dcl.init.general] p16.6:
4656+
// * Otherwise, if the destination type is a class type:
4657+
// * If the initializer expression is a prvalue and
4658+
// the cv-unqualified version of the source type is the same
4659+
// as the destination type, the initializer expression is used
4660+
// to initialize the destination object.
4661+
// * Otherwise, if the initialization is direct-initialization,
4662+
// or if it is copy-initialization where the cv-unqualified
4663+
// version of the source type is the same as or is derived from
4664+
// the class of the destination type, constructors are considered.
4665+
// The applicable constructors are enumerated, and the best one
4666+
// is chosen through overload resolution. Then:
4667+
// * If overload resolution is successful, the selected
4668+
// constructor is called to initialize the object, with
4669+
// the initializer expression or expression-list as its
4670+
// argument(s).
4671+
TryConstructorInitialization(S, Entity, Kind, Args, DestType, DestType,
4672+
Sequence, /*IsListInit=*/false, IsAggrListInit);
4673+
4674+
// * Otherwise, if no constructor is viable, the destination type
4675+
// is an aggregate class, and the initializer is a parenthesized
4676+
// expression-list, the object is initialized as follows. [...]
4677+
// Parenthesized initialization of aggregates is a C++20 feature.
4678+
if (S.getLangOpts().CPlusPlus20 &&
4679+
Kind.getKind() == InitializationKind::IK_Direct && Sequence.Failed() &&
4680+
Sequence.getFailureKind() ==
4681+
InitializationSequence::FK_ConstructorOverloadFailed &&
4682+
Sequence.getFailedOverloadResult() == OR_No_Viable_Function &&
4683+
(IsAggrListInit || DestType->isAggregateType()))
4684+
TryOrBuildParenListInitialization(S, Entity, Kind, Args, Sequence,
4685+
/*VerifyOnly=*/true);
4686+
4687+
// * Otherwise, the initialization is ill-formed.
4688+
}
4689+
46374690
static bool
46384691
ResolveOverloadedFunctionForReferenceBinding(Sema &S,
46394692
Expr *Initializer,
@@ -4847,11 +4900,16 @@ static void TryListInitialization(Sema &S,
48474900
QualType InitType = InitList->getInit(0)->getType();
48484901
if (S.Context.hasSameUnqualifiedType(InitType, DestType) ||
48494902
S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) {
4903+
InitializationKind SubKind =
4904+
Kind.getKind() == InitializationKind::IK_DirectList
4905+
? InitializationKind::CreateDirect(Kind.getLocation(),
4906+
InitList->getLBraceLoc(),
4907+
InitList->getRBraceLoc())
4908+
: Kind;
48504909
Expr *InitListAsExpr = InitList;
4851-
TryConstructorInitialization(S, Entity, Kind, InitListAsExpr, DestType,
4852-
DestType, Sequence,
4853-
/*InitListSyntax*/false,
4854-
/*IsInitListCopy*/true);
4910+
TryConstructorOrParenListInitialization(
4911+
S, Entity, SubKind, InitListAsExpr, DestType, Sequence,
4912+
/*IsAggrListInit=*/true);
48554913
return;
48564914
}
48574915
}
@@ -5710,7 +5768,7 @@ static void TryDefaultInitialization(Sema &S,
57105768
static void TryOrBuildParenListInitialization(
57115769
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
57125770
ArrayRef<Expr *> Args, InitializationSequence &Sequence, bool VerifyOnly,
5713-
ExprResult *Result = nullptr) {
5771+
ExprResult *Result) {
57145772
unsigned EntityIndexToProcess = 0;
57155773
SmallVector<Expr *, 4> InitExprs;
57165774
QualType ResultType;
@@ -6689,42 +6747,8 @@ void InitializationSequence::InitializeFrom(Sema &S,
66896747
(Context.hasSameUnqualifiedType(SourceType, DestType) ||
66906748
(Initializer && S.IsDerivedFrom(Initializer->getBeginLoc(),
66916749
SourceType, DestType))))) {
6692-
TryConstructorInitialization(S, Entity, Kind, Args, DestType, DestType,
6693-
*this);
6694-
6695-
// We fall back to the "no matching constructor" path if the
6696-
// failed candidate set has functions other than the three default
6697-
// constructors. For example, conversion function.
6698-
if (const auto *RD =
6699-
dyn_cast<CXXRecordDecl>(DestType->getAs<RecordType>()->getDecl());
6700-
// In general, we should call isCompleteType for RD to check its
6701-
// completeness, we don't call it here as it was already called in the
6702-
// above TryConstructorInitialization.
6703-
S.getLangOpts().CPlusPlus20 && RD && RD->hasDefinition() &&
6704-
RD->isAggregate() && Failed() &&
6705-
getFailureKind() == FK_ConstructorOverloadFailed) {
6706-
// Do not attempt paren list initialization if overload resolution
6707-
// resolves to a deleted function .
6708-
//
6709-
// We may reach this condition if we have a union wrapping a class with
6710-
// a non-trivial copy or move constructor and we call one of those two
6711-
// constructors. The union is an aggregate, but the matched constructor
6712-
// is implicitly deleted, so we need to prevent aggregate initialization
6713-
// (otherwise, it'll attempt aggregate initialization by initializing
6714-
// the first element with a reference to the union).
6715-
OverloadCandidateSet::iterator Best;
6716-
OverloadingResult OR = getFailedCandidateSet().BestViableFunction(
6717-
S, Kind.getLocation(), Best);
6718-
if (OR != OverloadingResult::OR_Deleted) {
6719-
// C++20 [dcl.init] 17.6.2.2:
6720-
// - Otherwise, if no constructor is viable, the destination type is
6721-
// an
6722-
// aggregate class, and the initializer is a parenthesized
6723-
// expression-list.
6724-
TryOrBuildParenListInitialization(S, Entity, Kind, Args, *this,
6725-
/*VerifyOnly=*/true);
6726-
}
6727-
}
6750+
TryConstructorOrParenListInitialization(S, Entity, Kind, Args, DestType,
6751+
*this, /*IsAggrListInit=*/false);
67286752
} else {
67296753
// - Otherwise (i.e., for the remaining copy-initialization cases),
67306754
// user-defined conversion sequences that can convert from the
Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,120 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
22

3-
// If the initializer is (), the object is value-initialized.
4-
5-
// expected-no-diagnostics
63
namespace GH69890 {
7-
struct A {
8-
constexpr A() {}
9-
int x;
10-
};
11-
12-
struct B : A {
13-
int y;
14-
};
15-
16-
static_assert(B().x == 0);
17-
static_assert(B().y == 0);
18-
}
4+
// If the initializer is (), the object is value-initialized.
5+
struct A {
6+
constexpr A() {}
7+
int x;
8+
};
9+
10+
struct B : A {
11+
int y;
12+
};
13+
14+
static_assert(B().x == 0);
15+
static_assert(B().y == 0);
16+
} // namespace GH69890
17+
18+
namespace P0960R3 {
19+
struct A { // expected-note 22 {{candidate constructor}}
20+
int i;
21+
operator int() volatile;
22+
};
23+
volatile A va;
24+
25+
A a1(va);
26+
A a2 = va; // expected-error {{no matching constructor for initialization of 'A'}}
27+
A a3 {va};
28+
A a4 = {va}; // expected-error {{no matching constructor for initialization of 'A'}}
29+
30+
A f() {
31+
return va; // expected-error {{no matching constructor for initialization of 'A'}}
32+
return {va}; // expected-error {{no matching constructor for initialization of 'A'}}
33+
}
34+
35+
int g(A); // expected-note 2 {{passing argument to parameter here}}
36+
int i = g(va); // expected-error {{no matching constructor for initialization of 'A'}}
37+
int j = g({va}); // expected-error {{no matching constructor for initialization of 'A'}}
38+
39+
struct Ambig {
40+
operator const A&(); // expected-note {{candidate function}}
41+
operator A&&(); // expected-note {{candidate function}}
42+
operator int();
43+
};
44+
45+
A a5(Ambig {}); // expected-error {{call to constructor of 'A' is ambiguous}}
46+
A a6 = Ambig {}; // expected-error {{conversion from 'Ambig' to 'A' is ambiguous}}
47+
A a7 {Ambig {}};
48+
A a8 = {Ambig {}};
49+
50+
A a9(1);
51+
A a10 = 1; // expected-error {{no viable conversion from 'int' to 'A'}}
52+
A a11 {1};
53+
A a12 = {1};
54+
55+
56+
struct B { // expected-note 12 {{candidate constructor}}
57+
int i;
58+
virtual operator int() volatile;
59+
};
60+
volatile B vb;
61+
62+
B b1(vb); // expected-error {{no matching constructor for initialization of 'B'}}
63+
B b2 = vb; // expected-error {{no matching constructor for initialization of 'B'}}
64+
B b3 {vb}; // expected-error {{no matching constructor for initialization of 'B'}}
65+
B b4 = {vb}; // expected-error {{no matching constructor for initialization of 'B'}}
66+
67+
68+
struct Immovable {
69+
Immovable();
70+
Immovable(const Immovable&) = delete; // #Imm_copy
71+
};
72+
73+
struct C { // #C
74+
int i;
75+
Immovable j; // #C_j
76+
77+
operator int() volatile;
78+
};
79+
C c;
80+
volatile C vc;
81+
82+
C c1(c); // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
83+
C c2 = c; // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
84+
C c3 {c}; // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
85+
C c4 = {c}; // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
86+
// expected-note@#C_j 4 {{copy constructor of 'C' is implicitly deleted}}
87+
// expected-note@#Imm_copy 4 {{'Immovable' has been explicitly marked deleted here}}
88+
89+
C c5(vc);
90+
C c6 = vc; // expected-error {{no matching constructor for initialization of 'C'}}
91+
C c7 {vc};
92+
C c8 = {vc}; // expected-error {{no matching constructor for initialization of 'C'}}
93+
// expected-note@#C 4 {{candidate constructor}}
94+
95+
C c9(C {});
96+
C c10 = C(123);
97+
C c11 {C {0, Immovable()}};
98+
C c12 = {C()};
99+
100+
101+
struct D { // expected-note 6 {{candidate constructor}}
102+
int i;
103+
};
104+
105+
struct DD : private D { // expected-note 4 {{declared private here}}
106+
virtual operator int() volatile;
107+
};
108+
DD dd;
109+
volatile DD vdd;
110+
111+
D d1(dd); // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
112+
D d2 = dd; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
113+
D d3 {dd}; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
114+
D d4 = {dd}; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
115+
116+
D d5(vdd);
117+
D d6 = vdd; // expected-error {{no matching constructor for initialization of 'D'}}
118+
D d7 {vdd};
119+
D d8 = {vdd}; // expected-error {{no matching constructor for initialization of 'D'}}
120+
} // namespace P0960R3

0 commit comments

Comments
 (0)