Skip to content

Commit db93ef1

Browse files
cor3ntinMitalAshok
andauthored
[Clang] Implement CWG2813: Class member access with prvalues (#120223)
This is a rebase of #95112 with my own feedback apply as @MitalAshok has been inactive for a while. It's fairly important this makes clang 20 as it is a blocker for #107451 --- [CWG2813](https://cplusplus.github.io/CWG/issues/2813.html) prvalue.member_fn(expression-list) now will not materialize a temporary for prvalue if member_fn is an explicit object member function, and prvalue will bind directly to the object parameter. The E1 in E1.static_member is now a discarded-value expression, so if E1 was a call to a [[nodiscard]] function, there will now be a warning. This also affects C++98 with [[gnu::warn_unused_result]] functions. This should not affect C where TemporaryMaterializationConversion is a no-op. Closes #100314 Fixes #100341 --------- Co-authored-by: Mital Ashok <[email protected]>
1 parent 66bdbfb commit db93ef1

File tree

13 files changed

+306
-76
lines changed

13 files changed

+306
-76
lines changed

clang-tools-extra/clangd/unittests/DumpASTTests.cpp

+37-4
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ declaration: Function - root
4949
)"},
5050
{R"cpp(
5151
namespace root {
52-
struct S { static const int x = 0; };
52+
struct S { static const int x = 0; ~S(); };
5353
int y = S::x + root::S().x;
5454
}
5555
)cpp",
@@ -60,10 +60,12 @@ declaration: Namespace - root
6060
type: Qualified - const
6161
type: Builtin - int
6262
expression: IntegerLiteral - 0
63+
declaration: CXXDestructor
64+
type: Record - S
65+
type: FunctionProto
66+
type: Builtin - void
6367
declaration: CXXConstructor
6468
declaration: CXXConstructor
65-
declaration: CXXConstructor
66-
declaration: CXXDestructor
6769
declaration: Var - y
6870
type: Builtin - int
6971
expression: ExprWithCleanups
@@ -74,14 +76,45 @@ declaration: Namespace - root
7476
type: Record - S
7577
expression: ImplicitCast - LValueToRValue
7678
expression: Member - x
77-
expression: MaterializeTemporary - rvalue
79+
expression: CXXBindTemporary
7880
expression: CXXTemporaryObject - S
7981
type: Elaborated
8082
specifier: Namespace - root::
8183
type: Record - S
8284
)"},
8385
{R"cpp(
8486
namespace root {
87+
struct S { static const int x = 0; };
88+
int y = S::x + root::S().x;
89+
}
90+
)cpp",
91+
R"(
92+
declaration: Namespace - root
93+
declaration: CXXRecord - S
94+
declaration: Var - x
95+
type: Qualified - const
96+
type: Builtin - int
97+
expression: IntegerLiteral - 0
98+
declaration: CXXConstructor
99+
declaration: CXXConstructor
100+
declaration: CXXConstructor
101+
declaration: CXXDestructor
102+
declaration: Var - y
103+
type: Builtin - int
104+
expression: BinaryOperator - +
105+
expression: ImplicitCast - LValueToRValue
106+
expression: DeclRef - x
107+
specifier: TypeSpec
108+
type: Record - S
109+
expression: ImplicitCast - LValueToRValue
110+
expression: Member - x
111+
expression: CXXTemporaryObject - S
112+
type: Elaborated
113+
specifier: Namespace - root::
114+
type: Record - S
115+
)"},
116+
{R"cpp(
117+
namespace root {
85118
template <typename T> int tmpl() {
86119
(void)tmpl<unsigned>();
87120
return T::value;

clang/docs/ReleaseNotes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@ Resolutions to C++ Defect Reports
321321
- Fix name lookup for a dependent base class that is the current instantiation.
322322
(`CWG591: When a dependent base class is the current instantiation <https://cplusplus.github.io/CWG/issues/591.html>`_).
323323

324+
- Clang now allows calling explicit object member functions directly with prvalues
325+
instead of always materializing a temporary, meaning by-value explicit object parameters
326+
do not need to move from a temporary.
327+
(`CWG2813: Class member access with prvalues <https://cplusplus.github.io/CWG/issues/2813.html>`_).
328+
324329
C Language Changes
325330
------------------
326331

clang/include/clang/Sema/Sema.h

+5
Original file line numberDiff line numberDiff line change
@@ -10659,6 +10659,11 @@ class Sema final : public SemaBase {
1065910659
SourceLocation EndLoc);
1066010660
void ActOnForEachDeclStmt(DeclGroupPtrTy Decl);
1066110661

10662+
/// DiagnoseDiscardedExprMarkedNodiscard - Given an expression that is
10663+
/// semantically a discarded-value expression, diagnose if any [[nodiscard]]
10664+
/// value has been discarded.
10665+
void DiagnoseDiscardedExprMarkedNodiscard(const Expr *E);
10666+
1066210667
/// DiagnoseUnusedExprResult - If the statement passed in is an expression
1066310668
/// whose result is unused, warn.
1066410669
void DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID);

clang/lib/AST/Expr.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,9 @@ bool Expr::isUnusedResultAWarning(const Expr *&WarnE, SourceLocation &Loc,
29902990
case ExprWithCleanupsClass:
29912991
return cast<ExprWithCleanups>(this)->getSubExpr()
29922992
->isUnusedResultAWarning(WarnE, Loc, R1, R2, Ctx);
2993+
case OpaqueValueExprClass:
2994+
return cast<OpaqueValueExpr>(this)->getSourceExpr()->isUnusedResultAWarning(
2995+
WarnE, Loc, R1, R2, Ctx);
29932996
}
29942997
}
29952998

clang/lib/Sema/SemaExprMember.cpp

+55-12
Original file line numberDiff line numberDiff line change
@@ -1003,15 +1003,6 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
10031003
: !isDependentScopeSpecifier(SS) || computeDeclContext(SS)) &&
10041004
"dependent lookup context that isn't the current instantiation?");
10051005

1006-
// C++1z [expr.ref]p2:
1007-
// For the first option (dot) the first expression shall be a glvalue [...]
1008-
if (!IsArrow && BaseExpr && BaseExpr->isPRValue()) {
1009-
ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
1010-
if (Converted.isInvalid())
1011-
return ExprError();
1012-
BaseExpr = Converted.get();
1013-
}
1014-
10151006
const DeclarationNameInfo &MemberNameInfo = R.getLookupNameInfo();
10161007
DeclarationName MemberName = MemberNameInfo.getName();
10171008
SourceLocation MemberLoc = MemberNameInfo.getLoc();
@@ -1128,26 +1119,68 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
11281119
BaseExpr = BuildCXXThisExpr(Loc, BaseExprType, /*IsImplicit=*/true);
11291120
}
11301121

1122+
// C++17 [expr.ref]p2, per CWG2813:
1123+
// For the first option (dot), if the id-expression names a static member or
1124+
// an enumerator, the first expression is a discarded-value expression; if
1125+
// the id-expression names a non-static data member, the first expression
1126+
// shall be a glvalue.
1127+
auto ConvertBaseExprToDiscardedValue = [&] {
1128+
assert(getLangOpts().CPlusPlus &&
1129+
"Static member / member enumerator outside of C++");
1130+
if (IsArrow)
1131+
return false;
1132+
ExprResult Converted = IgnoredValueConversions(BaseExpr);
1133+
if (Converted.isInvalid())
1134+
return true;
1135+
BaseExpr = Converted.get();
1136+
DiagnoseDiscardedExprMarkedNodiscard(BaseExpr);
1137+
return false;
1138+
};
1139+
auto ConvertBaseExprToGLValue = [&] {
1140+
if (IsArrow || !BaseExpr->isPRValue())
1141+
return false;
1142+
ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
1143+
if (Converted.isInvalid())
1144+
return true;
1145+
BaseExpr = Converted.get();
1146+
return false;
1147+
};
1148+
11311149
// Check the use of this member.
11321150
if (DiagnoseUseOfDecl(MemberDecl, MemberLoc))
11331151
return ExprError();
11341152

1135-
if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl))
1153+
if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl)) {
1154+
if (ConvertBaseExprToGLValue())
1155+
return ExprError();
11361156
return BuildFieldReferenceExpr(BaseExpr, IsArrow, OpLoc, SS, FD, FoundDecl,
11371157
MemberNameInfo);
1158+
}
11381159

1139-
if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl))
1160+
if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl)) {
1161+
// No temporaries are materialized for property references yet.
1162+
// They might be materialized when this is transformed into a member call.
1163+
// Note that this is slightly different behaviour from MSVC which doesn't
1164+
// implement CWG2813 yet: MSVC might materialize an extra temporary if the
1165+
// getter or setter function is an explicit object member function.
11401166
return BuildMSPropertyRefExpr(*this, BaseExpr, IsArrow, SS, PD,
11411167
MemberNameInfo);
1168+
}
11421169

1143-
if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl))
1170+
if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl)) {
1171+
if (ConvertBaseExprToGLValue())
1172+
return ExprError();
11441173
// We may have found a field within an anonymous union or struct
11451174
// (C++ [class.union]).
11461175
return BuildAnonymousStructUnionMemberReference(SS, MemberLoc, FD,
11471176
FoundDecl, BaseExpr,
11481177
OpLoc);
1178+
}
11491179

1180+
// Static data member
11501181
if (VarDecl *Var = dyn_cast<VarDecl>(MemberDecl)) {
1182+
if (ConvertBaseExprToDiscardedValue())
1183+
return ExprError();
11511184
return BuildMemberExpr(BaseExpr, IsArrow, OpLoc,
11521185
SS.getWithLocInContext(Context), TemplateKWLoc, Var,
11531186
FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1161,7 +1194,13 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
11611194
if (MemberFn->isInstance()) {
11621195
valueKind = VK_PRValue;
11631196
type = Context.BoundMemberTy;
1197+
if (MemberFn->isImplicitObjectMemberFunction() &&
1198+
ConvertBaseExprToGLValue())
1199+
return ExprError();
11641200
} else {
1201+
// Static member function
1202+
if (ConvertBaseExprToDiscardedValue())
1203+
return ExprError();
11651204
valueKind = VK_LValue;
11661205
type = MemberFn->getType();
11671206
}
@@ -1174,13 +1213,17 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
11741213
assert(!isa<FunctionDecl>(MemberDecl) && "member function not C++ method?");
11751214

11761215
if (EnumConstantDecl *Enum = dyn_cast<EnumConstantDecl>(MemberDecl)) {
1216+
if (ConvertBaseExprToDiscardedValue())
1217+
return ExprError();
11771218
return BuildMemberExpr(
11781219
BaseExpr, IsArrow, OpLoc, SS.getWithLocInContext(Context),
11791220
TemplateKWLoc, Enum, FoundDecl, /*HadMultipleCandidates=*/false,
11801221
MemberNameInfo, Enum->getType(), VK_PRValue, OK_Ordinary);
11811222
}
11821223

11831224
if (VarTemplateDecl *VarTempl = dyn_cast<VarTemplateDecl>(MemberDecl)) {
1225+
if (ConvertBaseExprToDiscardedValue())
1226+
return ExprError();
11841227
if (!TemplateArgs) {
11851228
diagnoseMissingTemplateArguments(
11861229
SS, /*TemplateKeyword=*/TemplateKWLoc.isValid(), VarTempl, MemberLoc);

clang/lib/Sema/SemaOverload.cpp

+3-8
Original file line numberDiff line numberDiff line change
@@ -5933,7 +5933,9 @@ ExprResult Sema::PerformImplicitObjectArgumentInitialization(
59335933
DestType = ImplicitParamRecordType;
59345934
FromClassification = From->Classify(Context);
59355935

5936-
// When performing member access on a prvalue, materialize a temporary.
5936+
// CWG2813 [expr.call]p6:
5937+
// If the function is an implicit object member function, the object
5938+
// expression of the class member access shall be a glvalue [...]
59375939
if (From->isPRValue()) {
59385940
From = CreateMaterializeTemporaryExpr(FromRecordType, From,
59395941
Method->getRefQualifier() !=
@@ -6464,11 +6466,6 @@ static Expr *GetExplicitObjectExpr(Sema &S, Expr *Obj,
64646466
VK_LValue, OK_Ordinary, SourceLocation(),
64656467
/*CanOverflow=*/false, FPOptionsOverride());
64666468
}
6467-
if (Obj->Classify(S.getASTContext()).isPRValue()) {
6468-
Obj = S.CreateMaterializeTemporaryExpr(
6469-
ObjType, Obj,
6470-
!Fun->getParamDecl(0)->getType()->isRValueReferenceType());
6471-
}
64726469
return Obj;
64736470
}
64746471

@@ -15584,8 +15581,6 @@ ExprResult Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE,
1558415581
CurFPFeatureOverrides(), Proto->getNumParams());
1558515582
} else {
1558615583
// Convert the object argument (for a non-static member function call).
15587-
// We only need to do this if there was actually an overload; otherwise
15588-
// it was done at lookup.
1558915584
ExprResult ObjectArg = PerformImplicitObjectArgumentInitialization(
1559015585
MemExpr->getBase(), Qualifier, FoundDecl, Method);
1559115586
if (ObjectArg.isInvalid())

0 commit comments

Comments
 (0)