Skip to content

[clang] Refine handling of C++20 aggregate initialization #131320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ Bug Fixes to C++ Support
- Fixed an assertion failure affecting code that uses C++23 "deducing this". (#GH130272)
- Clang now properly instantiates destructors for initialized members within non-delegating constructors. (#GH93251)
- Correctly diagnoses if unresolved using declarations shadows template paramters (#GH129411)
- Fixed C++20 aggregate initialization rules being incorrectly applied in certain contexts. (#GH131320)
- Clang was previously coalescing volatile writes to members of volatile base class subobjects.
The issue has been addressed by propagating qualifiers during derived-to-base conversions in the AST. (#GH127824)
- Fixed a Clang regression in C++20 mode where unresolved dependent call expressions were created inside non-dependent contexts (#GH122892)
Expand Down
106 changes: 65 additions & 41 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4634,6 +4634,59 @@ static void TryConstructorInitialization(Sema &S,
IsListInit | IsInitListCopy, AsInitializerList);
}

static void TryOrBuildParenListInitialization(
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
ArrayRef<Expr *> Args, InitializationSequence &Sequence, bool VerifyOnly,
ExprResult *Result = nullptr);

/// Attempt to initialize an object of a class type either by
/// direct-initialization, or by copy-initialization from an
/// expression of the same or derived class type. This corresponds
/// to the first two sub-bullets of C++2c [dcl.init.general] p16.6.
///
/// \param IsAggrListInit Is this non-list-initialization being done as
/// part of a list-initialization of an aggregate
/// from a single expression of the same or
/// derived class type (C++2c [dcl.init.list] p3.2)?
static void TryConstructorOrParenListInitialization(
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
MultiExprArg Args, QualType DestType, InitializationSequence &Sequence,
bool IsAggrListInit) {
// C++2c [dcl.init.general] p16.6:
// * Otherwise, if the destination type is a class type:
// * If the initializer expression is a prvalue and
// the cv-unqualified version of the source type is the same
// as the destination type, the initializer expression is used
// to initialize the destination object.
// * Otherwise, if the initialization is direct-initialization,
// or if it is copy-initialization where the cv-unqualified
// version of the source type is the same as or is derived from
// the class of the destination type, constructors are considered.
// The applicable constructors are enumerated, and the best one
// is chosen through overload resolution. Then:
// * If overload resolution is successful, the selected
// constructor is called to initialize the object, with
// the initializer expression or expression-list as its
// argument(s).
TryConstructorInitialization(S, Entity, Kind, Args, DestType, DestType,
Sequence, /*IsListInit=*/false, IsAggrListInit);

// * Otherwise, if no constructor is viable, the destination type
// is an aggregate class, and the initializer is a parenthesized
// expression-list, the object is initialized as follows. [...]
// Parenthesized initialization of aggregates is a C++20 feature.
if (S.getLangOpts().CPlusPlus20 &&
Kind.getKind() == InitializationKind::IK_Direct && Sequence.Failed() &&
Sequence.getFailureKind() ==
InitializationSequence::FK_ConstructorOverloadFailed &&
Sequence.getFailedOverloadResult() == OR_No_Viable_Function &&
(IsAggrListInit || DestType->isAggregateType()))
TryOrBuildParenListInitialization(S, Entity, Kind, Args, Sequence,
/*VerifyOnly=*/true);

// * Otherwise, the initialization is ill-formed.
}

static bool
ResolveOverloadedFunctionForReferenceBinding(Sema &S,
Expr *Initializer,
Expand Down Expand Up @@ -4847,11 +4900,16 @@ static void TryListInitialization(Sema &S,
QualType InitType = InitList->getInit(0)->getType();
if (S.Context.hasSameUnqualifiedType(InitType, DestType) ||
S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) {
InitializationKind SubKind =
Kind.getKind() == InitializationKind::IK_DirectList
? InitializationKind::CreateDirect(Kind.getLocation(),
InitList->getLBraceLoc(),
InitList->getRBraceLoc())
: Kind;
Expr *InitListAsExpr = InitList;
TryConstructorInitialization(S, Entity, Kind, InitListAsExpr, DestType,
DestType, Sequence,
/*InitListSyntax*/false,
/*IsInitListCopy*/true);
TryConstructorOrParenListInitialization(
S, Entity, SubKind, InitListAsExpr, DestType, Sequence,
/*IsAggrListInit=*/true);
return;
}
}
Expand Down Expand Up @@ -5710,7 +5768,7 @@ static void TryDefaultInitialization(Sema &S,
static void TryOrBuildParenListInitialization(
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
ArrayRef<Expr *> Args, InitializationSequence &Sequence, bool VerifyOnly,
ExprResult *Result = nullptr) {
ExprResult *Result) {
unsigned EntityIndexToProcess = 0;
SmallVector<Expr *, 4> InitExprs;
QualType ResultType;
Expand Down Expand Up @@ -6689,42 +6747,8 @@ void InitializationSequence::InitializeFrom(Sema &S,
(Context.hasSameUnqualifiedType(SourceType, DestType) ||
(Initializer && S.IsDerivedFrom(Initializer->getBeginLoc(),
SourceType, DestType))))) {
TryConstructorInitialization(S, Entity, Kind, Args, DestType, DestType,
*this);

// We fall back to the "no matching constructor" path if the
// failed candidate set has functions other than the three default
// constructors. For example, conversion function.
if (const auto *RD =
dyn_cast<CXXRecordDecl>(DestType->getAs<RecordType>()->getDecl());
// In general, we should call isCompleteType for RD to check its
// completeness, we don't call it here as it was already called in the
// above TryConstructorInitialization.
S.getLangOpts().CPlusPlus20 && RD && RD->hasDefinition() &&
RD->isAggregate() && Failed() &&
getFailureKind() == FK_ConstructorOverloadFailed) {
// Do not attempt paren list initialization if overload resolution
// resolves to a deleted function .
//
// We may reach this condition if we have a union wrapping a class with
// a non-trivial copy or move constructor and we call one of those two
// constructors. The union is an aggregate, but the matched constructor
// is implicitly deleted, so we need to prevent aggregate initialization
// (otherwise, it'll attempt aggregate initialization by initializing
// the first element with a reference to the union).
OverloadCandidateSet::iterator Best;
OverloadingResult OR = getFailedCandidateSet().BestViableFunction(
S, Kind.getLocation(), Best);
if (OR != OverloadingResult::OR_Deleted) {
// C++20 [dcl.init] 17.6.2.2:
// - Otherwise, if no constructor is viable, the destination type is
// an
// aggregate class, and the initializer is a parenthesized
// expression-list.
TryOrBuildParenListInitialization(S, Entity, Kind, Args, *this,
/*VerifyOnly=*/true);
}
}
TryConstructorOrParenListInitialization(S, Entity, Kind, Args, DestType,
*this, /*IsAggrListInit=*/false);
} else {
// - Otherwise (i.e., for the remaining copy-initialization cases),
// user-defined conversion sequences that can convert from the
Expand Down
132 changes: 117 additions & 15 deletions clang/test/CXX/dcl.decl/dcl.init/dcl.init.general/p16-cxx20.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,120 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s

// If the initializer is (), the object is value-initialized.

// expected-no-diagnostics
namespace GH69890 {
struct A {
constexpr A() {}
int x;
};

struct B : A {
int y;
};

static_assert(B().x == 0);
static_assert(B().y == 0);
}
// If the initializer is (), the object is value-initialized.
struct A {
constexpr A() {}
int x;
};

struct B : A {
int y;
};

static_assert(B().x == 0);
static_assert(B().y == 0);
} // namespace GH69890

namespace P0960R3 {
struct A { // expected-note 22 {{candidate constructor}}
int i;
operator int() volatile;
};
volatile A va;

A a1(va);
A a2 = va; // expected-error {{no matching constructor for initialization of 'A'}}
A a3 {va};
A a4 = {va}; // expected-error {{no matching constructor for initialization of 'A'}}

A f() {
return va; // expected-error {{no matching constructor for initialization of 'A'}}
return {va}; // expected-error {{no matching constructor for initialization of 'A'}}
}

int g(A); // expected-note 2 {{passing argument to parameter here}}
int i = g(va); // expected-error {{no matching constructor for initialization of 'A'}}
int j = g({va}); // expected-error {{no matching constructor for initialization of 'A'}}

struct Ambig {
operator const A&(); // expected-note {{candidate function}}
operator A&&(); // expected-note {{candidate function}}
operator int();
};

A a5(Ambig {}); // expected-error {{call to constructor of 'A' is ambiguous}}
A a6 = Ambig {}; // expected-error {{conversion from 'Ambig' to 'A' is ambiguous}}
A a7 {Ambig {}};
A a8 = {Ambig {}};

A a9(1);
A a10 = 1; // expected-error {{no viable conversion from 'int' to 'A'}}
A a11 {1};
A a12 = {1};


struct B { // expected-note 12 {{candidate constructor}}
int i;
virtual operator int() volatile;
};
volatile B vb;

B b1(vb); // expected-error {{no matching constructor for initialization of 'B'}}
B b2 = vb; // expected-error {{no matching constructor for initialization of 'B'}}
B b3 {vb}; // expected-error {{no matching constructor for initialization of 'B'}}
B b4 = {vb}; // expected-error {{no matching constructor for initialization of 'B'}}


struct Immovable {
Immovable();
Immovable(const Immovable&) = delete; // #Imm_copy
};

struct C { // #C
int i;
Immovable j; // #C_j

operator int() volatile;
};
C c;
volatile C vc;

C c1(c); // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
C c2 = c; // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
C c3 {c}; // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
C c4 = {c}; // expected-error {{call to implicitly-deleted copy constructor of 'C'}}
// expected-note@#C_j 4 {{copy constructor of 'C' is implicitly deleted}}
// expected-note@#Imm_copy 4 {{'Immovable' has been explicitly marked deleted here}}

C c5(vc);
C c6 = vc; // expected-error {{no matching constructor for initialization of 'C'}}
C c7 {vc};
C c8 = {vc}; // expected-error {{no matching constructor for initialization of 'C'}}
// expected-note@#C 4 {{candidate constructor}}

C c9(C {});
C c10 = C(123);
C c11 {C {0, Immovable()}};
C c12 = {C()};


struct D { // expected-note 6 {{candidate constructor}}
int i;
};

struct DD : private D { // expected-note 4 {{declared private here}}
virtual operator int() volatile;
};
DD dd;
volatile DD vdd;

D d1(dd); // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
D d2 = dd; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
D d3 {dd}; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}
D d4 = {dd}; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}}

D d5(vdd);
D d6 = vdd; // expected-error {{no matching constructor for initialization of 'D'}}
D d7 {vdd};
D d8 = {vdd}; // expected-error {{no matching constructor for initialization of 'D'}}
} // namespace P0960R3
Loading