diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4485,10 +4485,16 @@ CES_AllowParameters = 1, CES_AllowDifferentTypes = 2, CES_AllowExceptionVariables = 4, - CES_FormerDefault = (CES_AllowParameters), - CES_Default = (CES_AllowParameters | CES_AllowDifferentTypes), - CES_AsIfByStdMove = (CES_AllowParameters | CES_AllowDifferentTypes | - CES_AllowExceptionVariables), + CES_AllowRValueReferenceType = 8, + CES_ImplicitlyMovableCXX11 = (CES_AllowParameters), + CES_ImplicitlyMovableCXX14CXX17 = + (CES_AllowParameters | CES_AllowDifferentTypes), + CES_ImplicitlyMovableCXX20 = + (CES_AllowParameters | CES_AllowDifferentTypes | + CES_AllowRValueReferenceType), + CES_AsIfByStdMove = + (CES_AllowParameters | CES_AllowDifferentTypes | + CES_AllowExceptionVariables | CES_AllowRValueReferenceType), }; VarDecl *getCopyElisionCandidate(QualType ReturnType, Expr *E, diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3056,12 +3056,20 @@ // variable will no longer be used. if (VD->hasAttr()) return false; + // ...non-volatile... + if (VD->getType().isVolatileQualified()) + return false; + + // C++20 [class.copy.elision]p3: + // ...rvalue reference to a non-volatile... + if (VD->getType()->isRValueReferenceType() && + (!(CESK & CES_AllowRValueReferenceType) || + VD->getType().getNonReferenceType().isVolatileQualified())) + return false; + if (CESK & CES_AllowDifferentTypes) return true; - // ...non-volatile... - if (VD->getType().isVolatileQualified()) return false; - // Variables with higher required alignment than their type's ABI // alignment cannot use NRVO. if (!VD->getType()->isDependentType() && VD->hasAttr() && @@ -3074,13 +3082,13 @@ /// Try to perform the initialization of a potentially-movable value, /// which is the operand to a return or throw statement. /// -/// This routine implements C++14 [class.copy]p32, which attempts to treat -/// returned lvalues as rvalues in certain cases (to prefer move construction), -/// then falls back to treating them as lvalues if that failed. +/// This routine implements C++20 [class.copy.elision]p3:, which attempts to +/// treat returned lvalues as rvalues in certain cases (to prefer move +/// construction), then falls back to treating them as lvalues if that failed. /// -/// \param ConvertingConstructorsOnly If true, follow [class.copy]p32 and reject -/// resolutions that find non-constructors, such as derived-to-base conversions -/// or `operator T()&&` member functions. If false, do consider such +/// \param ConvertingConstructorsOnly If true, follow [class.copy.elision]p3 and +/// reject resolutions that find non-constructors, such as derived-to-base +/// conversions or `operator T()&&` member functions. If false, do consider such /// conversion sequences. /// /// \param Res We will fill this in if move-initialization was possible. @@ -3114,10 +3122,14 @@ FunctionDecl *FD = Step.Function.Function; if (ConvertingConstructorsOnly) { - if (isa(FD)) { + if (!isa(FD)) { + continue; + } + if (!S.getLangOpts().CPlusPlus20) { + // C++11 [class.copy]p32: // C++14 [class.copy]p32: - // [...] If the first overload resolution fails or was not performed, - // or if the type of the first parameter of the selected constructor + // C++17 [class.copy.elision]p3: + // [...] if the type of the first parameter of the selected constructor // is not an rvalue reference to the object's type (possibly // cv-qualified), overload resolution is performed again, considering // the object as an lvalue. @@ -3128,8 +3140,6 @@ if (!S.Context.hasSameUnqualifiedType(RRefType->getPointeeType(), NRVOCandidate->getType())) break; - } else { - continue; } } else { if (isa(FD)) { @@ -3163,36 +3173,48 @@ /// Perform the initialization of a potentially-movable value, which /// is the result of return value. /// -/// This routine implements C++14 [class.copy]p32, which attempts to treat -/// returned lvalues as rvalues in certain cases (to prefer move construction), -/// then falls back to treating them as lvalues if that failed. +/// This routine implements C++20 [class.copy.elision]p3, which attempts to +/// treat returned lvalues as rvalues in certain cases (to prefer move +/// construction), then falls back to treating them as lvalues if that failed. +/// +/// Standard revision history: +/// C++11 +/// CWG1579:-returned object type and function return type can be different. +/// C++14 +/// |--just rephrase rule. +/// C++17 +/// P0912:-add support for co_return. +/// P1825:-returned object can be rvalue reference to non-volatile. +/// -the first parameter of overload resolution selected function does +/// not need to be an rvalue reference to the returned object's type. +/// C++20 + ExprResult Sema::PerformMoveOrCopyInitialization(const InitializedEntity &Entity, const VarDecl *NRVOCandidate, QualType ResultType, Expr *Value, bool AllowNRVO) { - // C++14 [class.copy]p32: - // When the criteria for elision of a copy/move operation are met, but not for - // an exception-declaration, and the object to be copied is designated by an - // lvalue, or when the expression in a return statement is a (possibly - // parenthesized) id-expression that names an object with automatic storage - // duration declared in the body or parameter-declaration-clause of the - // innermost enclosing function or lambda-expression, overload resolution to - // select the constructor for the copy is first performed as if the object - // were designated by an rvalue. ExprResult Res = ExprError(); if (AllowNRVO) { - bool AffectedByCWG1579 = false; + CopyElisionSemanticsKind CESK = CES_Strict; + if (getLangOpts().CPlusPlus20) { + CESK = CES_ImplicitlyMovableCXX20; + } else if (getLangOpts().CPlusPlus14 || LangOptions().CPlusPlus17) { + CESK = CES_ImplicitlyMovableCXX14CXX17; + } else if (getLangOpts().CPlusPlus11) { + CESK = CES_ImplicitlyMovableCXX11; + } + bool AffectedByCWG1579 = false; if (!NRVOCandidate) { - NRVOCandidate = getCopyElisionCandidate(ResultType, Value, CES_Default); + NRVOCandidate = getCopyElisionCandidate(ResultType, Value, CESK); if (NRVOCandidate && !getDiagnostics().isIgnored(diag::warn_return_std_move_in_cxx11, Value->getExprLoc())) { - const VarDecl *NRVOCandidateInCXX11 = - getCopyElisionCandidate(ResultType, Value, CES_FormerDefault); + const VarDecl *NRVOCandidateInCXX11 = getCopyElisionCandidate( + ResultType, Value, CES_ImplicitlyMovableCXX11); AffectedByCWG1579 = (!NRVOCandidateInCXX11); } } @@ -3266,6 +3288,7 @@ // Either we didn't meet the criteria for treating an lvalue as an rvalue, // above, or overload resolution failed. Either way, we need to try // (again) now with the return value expression as written. + // TODO: C++20 considering the expression or operand as an lvalue if (Res.isInvalid()) Res = PerformCopyInitialization(Entity, SourceLocation(), Value); diff --git a/clang/test/SemaCXX/implicit-move.cpp b/clang/test/SemaCXX/implicit-move.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/implicit-move.cpp @@ -0,0 +1,80 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fcxx-exceptions -DCXX20 -verify %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -fcxx-exceptions -DCXX17 -verify %s +// RUN: %clang_cc1 -std=c++14 -fsyntax-only -fcxx-exceptions -DCXX14 -verify %s + +class Error; + +class NeedRvalueRef { +public: + NeedRvalueRef() {} + ~NeedRvalueRef() {} + NeedRvalueRef(const NeedRvalueRef &); + NeedRvalueRef(NeedRvalueRef &&); + NeedRvalueRef(Error &&); +}; + +class NoNeedRvalueRef { +public: + NoNeedRvalueRef() {} + ~NoNeedRvalueRef() {} + NoNeedRvalueRef(const NoNeedRvalueRef &); + NoNeedRvalueRef(NoNeedRvalueRef &&); + NoNeedRvalueRef(Error); +}; + +#ifdef CXX20 +// expected-no-diagnostics +class Error { +public: + Error() {} + ~Error() {} + Error(Error &&); + +private: + Error(const Error &); +}; + +NoNeedRvalueRef test() { + Error Err; + return Err; +} +#endif + +#ifdef CXX17 +class Error { +public: + Error() {} + ~Error() {} + Error(Error &&); + +private: + Error(const Error &); // expected-note {{declared private here}} +}; + +NoNeedRvalueRef test2() { + Error Err; + return Err; // expected-error {{calling a private constructor of class 'Error'}} +} +#endif + +#ifdef CXX14 +class Error { +public: + Error() {} + ~Error() {} + Error(Error &&); + +private: + Error(const Error &); // expected-note {{declared private here}} +}; + +NoNeedRvalueRef test2() { + Error Err; + return Err; // expected-error {{calling a private constructor of class 'Error'}} +} +#endif + +NeedRvalueRef test_all_ok() { + Error Err; + return Err; +} \ No newline at end of file diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -1201,6 +1201,11 @@ P0593R6 (DR) Clang 11 + + More implicit moves + P1825R0 (DR) + Clang 12 (partial) +