Skip to content

[Clang] Implement CWG2813: Class member access with prvalues #95112

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

Closed
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
41 changes: 37 additions & 4 deletions clang-tools-extra/clangd/unittests/DumpASTTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ declaration: Function - root
)"},
{R"cpp(
namespace root {
struct S { static const int x = 0; };
struct S { static const int x = 0; ~S(); };
int y = S::x + root::S().x;
}
)cpp",
Expand All @@ -60,10 +60,12 @@ declaration: Namespace - root
type: Qualified - const
type: Builtin - int
expression: IntegerLiteral - 0
declaration: CXXDestructor
type: Record - S
type: FunctionProto
type: Builtin - void
declaration: CXXConstructor
declaration: CXXConstructor
declaration: CXXConstructor
declaration: CXXDestructor
declaration: Var - y
type: Builtin - int
expression: ExprWithCleanups
Expand All @@ -74,14 +76,45 @@ declaration: Namespace - root
type: Record - S
expression: ImplicitCast - LValueToRValue
expression: Member - x
expression: MaterializeTemporary - rvalue
expression: CXXBindTemporary
expression: CXXTemporaryObject - S
type: Elaborated
specifier: Namespace - root::
type: Record - S
)"},
{R"cpp(
namespace root {
struct S { static const int x = 0; };
int y = S::x + root::S().x;
}
)cpp",
R"(
declaration: Namespace - root
declaration: CXXRecord - S
declaration: Var - x
type: Qualified - const
type: Builtin - int
expression: IntegerLiteral - 0
declaration: CXXConstructor
declaration: CXXConstructor
declaration: CXXConstructor
declaration: CXXDestructor
declaration: Var - y
type: Builtin - int
expression: BinaryOperator - +
expression: ImplicitCast - LValueToRValue
expression: DeclRef - x
specifier: TypeSpec
type: Record - S
expression: ImplicitCast - LValueToRValue
expression: Member - x
expression: CXXTemporaryObject - S
type: Elaborated
specifier: Namespace - root::
type: Record - S
)"},
{R"cpp(
namespace root {
template <typename T> int tmpl() {
(void)tmpl<unsigned>();
return T::value;
Expand Down
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ C++2c Feature Support
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

C Language Changes
------------------

Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -10673,6 +10673,11 @@ class Sema final : public SemaBase {
SourceLocation EndLoc);
void ActOnForEachDeclStmt(DeclGroupPtrTy Decl);

/// DiagnoseDiscardedNodiscard - Given an expression that is semantically
/// a discarded-value expression, diagnose if any [[nodiscard]] value
/// has been discarded
void DiagnoseDiscardedNodiscard(const Expr *E);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we find a better name for that function?


/// DiagnoseUnusedExprResult - If the statement passed in is an expression
/// whose result is unused, warn.
void DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID);
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2973,6 +2973,9 @@ bool Expr::isUnusedResultAWarning(const Expr *&WarnE, SourceLocation &Loc,
case ExprWithCleanupsClass:
return cast<ExprWithCleanups>(this)->getSubExpr()
->isUnusedResultAWarning(WarnE, Loc, R1, R2, Ctx);
case OpaqueValueExprClass:
return cast<OpaqueValueExpr>(this)->getSourceExpr()->isUnusedResultAWarning(
WarnE, Loc, R1, R2, Ctx);
}
}

Expand Down
64 changes: 52 additions & 12 deletions clang/lib/Sema/SemaExprMember.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1004,15 +1004,6 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
: !isDependentScopeSpecifier(SS) || computeDeclContext(SS)) &&
"dependent lookup context that isn't the current instantiation?");

// C++1z [expr.ref]p2:
// For the first option (dot) the first expression shall be a glvalue [...]
if (!IsArrow && BaseExpr && BaseExpr->isPRValue()) {
ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
if (Converted.isInvalid())
return ExprError();
BaseExpr = Converted.get();
}

const DeclarationNameInfo &MemberNameInfo = R.getLookupNameInfo();
DeclarationName MemberName = MemberNameInfo.getName();
SourceLocation MemberLoc = MemberNameInfo.getLoc();
Expand Down Expand Up @@ -1129,26 +1120,68 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
BaseExpr = BuildCXXThisExpr(Loc, BaseExprType, /*IsImplicit=*/true);
}

// C++17 [expr.ref]p2, per CWG2813:
// For the first option (dot), if the id-expression names a static member or
// an enumerator, the first expression is a discarded-value expression; if
// the id-expression names a non-static data member, the first expression
// shall be a glvalue.
auto MakeDiscardedValue = [&] {
assert(getLangOpts().CPlusPlus &&
"Static member / member enumerator outside of C++");
if (IsArrow)
return false;
ExprResult Converted = IgnoredValueConversions(BaseExpr);
if (Converted.isInvalid())
return true;
BaseExpr = Converted.get();
DiagnoseDiscardedNodiscard(BaseExpr);
return false;
};
auto MakeGLValue = [&] {
if (IsArrow || !BaseExpr->isPRValue())
return false;
ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
if (Converted.isInvalid())
return true;
BaseExpr = Converted.get();
return false;
};

// Check the use of this member.
if (DiagnoseUseOfDecl(MemberDecl, MemberLoc))
return ExprError();

if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl))
if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl)) {
if (MakeGLValue())
return ExprError();
return BuildFieldReferenceExpr(BaseExpr, IsArrow, OpLoc, SS, FD, FoundDecl,
MemberNameInfo);
}

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

if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl))
if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl)) {
if (MakeGLValue())
return ExprError();
// We may have found a field within an anonymous union or struct
// (C++ [class.union]).
return BuildAnonymousStructUnionMemberReference(SS, MemberLoc, FD,
FoundDecl, BaseExpr,
OpLoc);
}

// Static data member
if (VarDecl *Var = dyn_cast<VarDecl>(MemberDecl)) {
if (MakeDiscardedValue())
return ExprError();
return BuildMemberExpr(BaseExpr, IsArrow, OpLoc,
SS.getWithLocInContext(Context), TemplateKWLoc, Var,
FoundDecl, /*HadMultipleCandidates=*/false,
Expand All @@ -1163,6 +1196,9 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
valueKind = VK_PRValue;
type = Context.BoundMemberTy;
} else {
// Static member function
if (MakeDiscardedValue())
return ExprError();
valueKind = VK_LValue;
type = MemberFn->getType();
}
Expand All @@ -1175,13 +1211,17 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
assert(!isa<FunctionDecl>(MemberDecl) && "member function not C++ method?");

if (EnumConstantDecl *Enum = dyn_cast<EnumConstantDecl>(MemberDecl)) {
if (MakeDiscardedValue())
return ExprError();
return BuildMemberExpr(
BaseExpr, IsArrow, OpLoc, SS.getWithLocInContext(Context),
TemplateKWLoc, Enum, FoundDecl, /*HadMultipleCandidates=*/false,
MemberNameInfo, Enum->getType(), VK_PRValue, OK_Ordinary);
}

if (VarTemplateDecl *VarTempl = dyn_cast<VarTemplateDecl>(MemberDecl)) {
if (MakeDiscardedValue())
return ExprError();
if (!TemplateArgs) {
diagnoseMissingTemplateArguments(
SS, /*TemplateKeyword=*/TemplateKWLoc.isValid(), VarTempl, MemberLoc);
Expand Down
11 changes: 3 additions & 8 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5825,7 +5825,9 @@ ExprResult Sema::PerformImplicitObjectArgumentInitialization(
DestType = ImplicitParamRecordType;
FromClassification = From->Classify(Context);

// When performing member access on a prvalue, materialize a temporary.
// CWG2813 [expr.call]p6:
// If the function is an implicit object member function, the object
// expression of the class member access shall be a glvalue [...]
if (From->isPRValue()) {
From = CreateMaterializeTemporaryExpr(FromRecordType, From,
Method->getRefQualifier() !=
Expand Down Expand Up @@ -6353,11 +6355,6 @@ static Expr *GetExplicitObjectExpr(Sema &S, Expr *Obj,
VK_LValue, OK_Ordinary, SourceLocation(),
/*CanOverflow=*/false, FPOptionsOverride());
}
if (Obj->Classify(S.getASTContext()).isPRValue()) {
Obj = S.CreateMaterializeTemporaryExpr(
ObjType, Obj,
!Fun->getParamDecl(0)->getType()->isRValueReferenceType());
}
return Obj;
}

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