Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -380,7 +380,11 @@ def ExplicitInitializeCall : DiagGroup<"explicit-initialize-call">; def Packed : DiagGroup<"packed">; def Padded : DiagGroup<"padded">; + def PessimizingMove : DiagGroup<"pessimizing-move">; +def ReturnStdMoveInCXX11 : DiagGroup<"return-std-move-in-c++11">; +def ReturnStdMove : DiagGroup<"return-std-move">; + def PointerArith : DiagGroup<"pointer-arith">; def PoundWarning : DiagGroup<"#warnings">; def PoundPragmaMessage : DiagGroup<"#pragma-messages">, Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -19,6 +19,14 @@ def note_callee_decl : Note<"%0 declared here">; def note_defined_here : Note<"%0 defined here">; +def warn_return_std_move : Warning< + "adding 'std::move' here would select a better conversion sequence">, + InGroup, DefaultIgnore; + +def warn_return_std_move_in_cxx11 : Warning< + "adding 'std::move' here would have selected a better conversion sequence prior to CWG 1579">, + InGroup, DefaultIgnore; + // For loop analysis def warn_variables_not_in_loop_body : Warning< "variable%select{s| %1|s %1 and %2|s %1, %2, and %3|s %1, %2, %3, and %4}0 " Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -3810,10 +3810,21 @@ RecordDecl *CreateCapturedStmtRecordDecl(CapturedDecl *&CD, SourceLocation Loc, unsigned NumParams); + + enum CopyElisionSemanticsKind { + CES_Strict = 0, + CES_AllowParameters = 1, + CES_AllowMoveConstructible = 2, + CES_AllowExceptionVariables = 4, + CES_FormerDefault = (CES_AllowParameters), + CES_Default = (CES_AllowParameters | CES_AllowMoveConstructible), + CES_AsIfByStdMove = (CES_AllowParameters | CES_AllowMoveConstructible | CES_AllowExceptionVariables), + }; + VarDecl *getCopyElisionCandidate(QualType ReturnType, Expr *E, - bool AllowParamOrMoveConstructible); + CopyElisionSemanticsKind CESK); bool isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD, - bool AllowParamOrMoveConstructible); + CopyElisionSemanticsKind CESK); StmtResult ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, Scope *CurScope); Index: lib/Sema/SemaExprCXX.cpp =================================================================== --- lib/Sema/SemaExprCXX.cpp +++ lib/Sema/SemaExprCXX.cpp @@ -728,7 +728,7 @@ // exception object const VarDecl *NRVOVariable = nullptr; if (IsThrownVarInScope) - NRVOVariable = getCopyElisionCandidate(QualType(), Ex, false); + NRVOVariable = getCopyElisionCandidate(QualType(), Ex, CES_Strict); InitializedEntity Entity = InitializedEntity::InitializeException( OpLoc, ExceptionObjectTy, Index: lib/Sema/SemaStmt.cpp =================================================================== --- lib/Sema/SemaStmt.cpp +++ lib/Sema/SemaStmt.cpp @@ -2858,7 +2858,7 @@ /// \param E The expression being returned from the function or block, or /// being thrown. /// -/// \param AllowParamOrMoveConstructible Whether we allow function parameters or +/// \param CESK Whether we allow function parameters or /// id-expressions that could be moved out of the function to be considered NRVO /// candidates. C++ prohibits these for NRVO itself, but we re-use this logic to /// determine whether we should try to move as part of a return or throw (which @@ -2867,7 +2867,7 @@ /// \returns The NRVO candidate variable, if the return statement may use the /// NRVO, or NULL if there is no such candidate. VarDecl *Sema::getCopyElisionCandidate(QualType ReturnType, Expr *E, - bool AllowParamOrMoveConstructible) { + CopyElisionSemanticsKind CESK) { if (!getLangOpts().CPlusPlus) return nullptr; @@ -2880,13 +2880,13 @@ if (!VD) return nullptr; - if (isCopyElisionCandidate(ReturnType, VD, AllowParamOrMoveConstructible)) + if (isCopyElisionCandidate(ReturnType, VD, CESK)) return VD; return nullptr; } bool Sema::isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD, - bool AllowParamOrMoveConstructible) { + CopyElisionSemanticsKind CESK) { QualType VDType = VD->getType(); // - in a return statement in a function with ... // ... a class return type ... @@ -2895,16 +2895,17 @@ return false; // ... the same cv-unqualified type as the function return type ... // When considering moving this expression out, allow dissimilar types. - if (!AllowParamOrMoveConstructible && !VDType->isDependentType() && + if (!(CESK & CES_AllowMoveConstructible) && !VDType->isDependentType() && !Context.hasSameUnqualifiedType(ReturnType, VDType)) return false; } // ...object (other than a function or catch-clause parameter)... if (VD->getKind() != Decl::Var && - !(AllowParamOrMoveConstructible && VD->getKind() == Decl::ParmVar)) + !((CESK & CES_AllowParameters) && VD->getKind() == Decl::ParmVar)) + return false; + if (!(CESK & CES_AllowExceptionVariables) && VD->isExceptionVariable()) return false; - if (VD->isExceptionVariable()) return false; // ...automatic... if (!VD->hasLocalStorage()) return false; @@ -2914,7 +2915,7 @@ // variable will no longer be used. if (VD->hasAttr()) return false; - if (AllowParamOrMoveConstructible) + if (CESK & CES_AllowMoveConstructible) return true; // ...non-volatile... @@ -2929,6 +2930,70 @@ return true; } +static void AttemptMoveInitialization(Sema& S, + const InitializedEntity &Entity, + const VarDecl *NRVOCandidate, + QualType ResultType, + Expr *Value, + bool IsFake, + ExprResult &Res) +{ + ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack, Value->getType(), + CK_NoOp, Value, VK_XValue); + + Expr *InitExpr = &AsRvalue; + + InitializationKind Kind = InitializationKind::CreateCopy( + Value->getLocStart(), Value->getLocStart()); + + InitializationSequence Seq(S, Entity, Kind, InitExpr); + if (Seq) { + for (const InitializationSequence::Step &Step : Seq.steps()) { + if (!(Step.Kind == + InitializationSequence::SK_ConstructorInitialization || + Step.Kind == InitializationSequence::SK_UserConversion)) + continue; + + FunctionDecl *FD = Step.Function.Function; + if (isa(FD)) { + if (IsFake) { + // Check that overload resolution selected a constructor taking an + // rvalue reference. If it selected an lvalue reference, then we + // didn't need to cast this thing to an rvalue in the first place. + if (!isa(FD->getParamDecl(0)->getType())) + break; + } else { + // [...] If the first overload resolution fails or was not performed, or + // 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. + const RValueReferenceType *RRefType = FD->getParamDecl(0)->getType()->getAs(); + if (!RRefType) + break; + if (!S.Context.hasSameUnqualifiedType(RRefType->getPointeeType(), + NRVOCandidate->getType())) + break; + } + } else if (IsFake && isa(FD)) { + if (cast(FD)->getRefQualifier() != RQ_RValue) + break; + } else { + continue; + } + + // Promote "AsRvalue" to the heap, since we now need this + // expression node to persist. + Value = ImplicitCastExpr::Create(S.Context, Value->getType(), CK_NoOp, + Value, nullptr, VK_XValue); + + // Complete type-checking the initialization of the return type + // using the constructor we found. + Res = Seq.Perform(S, Entity, Kind, Value); + } + } +} + /// \brief Perform the initialization of a potentially-movable value, which /// is the result of return value. /// @@ -2952,52 +3017,47 @@ // were designated by an rvalue. ExprResult Res = ExprError(); - if (AllowNRVO && !NRVOCandidate) - NRVOCandidate = getCopyElisionCandidate(ResultType, Value, true); - - if (AllowNRVO && NRVOCandidate) { - ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack, Value->getType(), - CK_NoOp, Value, VK_XValue); - - Expr *InitExpr = &AsRvalue; - - InitializationKind Kind = InitializationKind::CreateCopy( - Value->getLocStart(), Value->getLocStart()); + if (AllowNRVO) { + bool AffectedByCWG1579 = false; - InitializationSequence Seq(*this, Entity, Kind, InitExpr); - if (Seq) { - for (const InitializationSequence::Step &Step : Seq.steps()) { - if (!(Step.Kind == - InitializationSequence::SK_ConstructorInitialization || - (Step.Kind == InitializationSequence::SK_UserConversion && - isa(Step.Function.Function)))) - continue; - - CXXConstructorDecl *Constructor = - cast(Step.Function.Function); - - const RValueReferenceType *RRefType - = Constructor->getParamDecl(0)->getType() - ->getAs(); - - // [...] If the first overload resolution fails or was not performed, or - // 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. - if (!RRefType || - !Context.hasSameUnqualifiedType(RRefType->getPointeeType(), - NRVOCandidate->getType())) - break; + if (!NRVOCandidate) { + NRVOCandidate = getCopyElisionCandidate(ResultType, Value, CES_Default); + if (NRVOCandidate && !getDiagnostics().isIgnored(diag::warn_return_std_move_in_cxx11, Value->getExprLoc())) { + const auto *NRVOCandidateInCXX11 = getCopyElisionCandidate(ResultType, Value, CES_FormerDefault); + AffectedByCWG1579 = (!NRVOCandidateInCXX11); + } + } - // Promote "AsRvalue" to the heap, since we now need this - // expression node to persist. - Value = ImplicitCastExpr::Create(Context, Value->getType(), CK_NoOp, - Value, nullptr, VK_XValue); + if (NRVOCandidate) { + AttemptMoveInitialization(*this, Entity, NRVOCandidate, ResultType, Value, false, Res); + } - // Complete type-checking the initialization of the return type - // using the constructor we found. - Res = Seq.Perform(*this, Entity, Kind, Value); + if (!Res.isInvalid() && AffectedByCWG1579) { + const auto QT = NRVOCandidate->getType(); + if (QT->isLValueReferenceType()) { + // Adding 'std::move' around an lvalue reference variable's name is dangerous. Don't suggest it. + } else if (QT.getNonReferenceType().getUnqualifiedType().isTriviallyCopyableType(Context)) { + // Adding 'std::move' around a trivially copyable variable is probably pointless. Don't suggest it. + } else { + // The most common case for this is returning T from a function that returns Expected. + // This is totally fine in a post-CWG1579 world, but was not fine before. + Diag(Value->getExprLoc(), diag::warn_return_std_move_in_cxx11); + } + } else if (Res.isInvalid() && !getDiagnostics().isIgnored(diag::warn_return_std_move, Value->getExprLoc())) { + const VarDecl *FakeNRVOCandidate = getCopyElisionCandidate(QualType(), Value, CES_AsIfByStdMove); + if (FakeNRVOCandidate) { + const auto QT = FakeNRVOCandidate->getType(); + if (QT->isLValueReferenceType() && !FakeNRVOCandidate->isExceptionVariable()) { + // Adding 'std::move' around an lvalue reference variable's name is dangerous. Don't suggest it. + } else if (QT.getNonReferenceType().getUnqualifiedType().isTriviallyCopyableType(Context)) { + // Adding 'std::move' around a trivially copyable variable is probably pointless. Don't suggest it. + } else { + ExprResult FakeRes = ExprError(); + AttemptMoveInitialization(*this, Entity, FakeNRVOCandidate, ResultType, Value, true, FakeRes); + if (!FakeRes.isInvalid()) { + Diag(Value->getExprLoc(), diag::warn_return_std_move); + } + } } } } @@ -3145,7 +3205,7 @@ // In C++ the return statement is handled via a copy initialization. // the C version of which boils down to CheckSingleAssignmentConstraints. - NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, false); + NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, CES_Strict); InitializedEntity Entity = InitializedEntity::InitializeResult(ReturnLoc, FnRetType, NRVOCandidate != nullptr); @@ -3158,7 +3218,7 @@ RetValExp = Res.get(); CheckReturnValExpr(RetValExp, FnRetType, ReturnLoc); } else { - NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, false); + NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, CES_Strict); } if (RetValExp) { @@ -3528,7 +3588,7 @@ // In C++ the return statement is handled via a copy initialization, // the C version of which boils down to CheckSingleAssignmentConstraints. if (RetValExp) - NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, false); + NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, CES_Strict); if (!HasDependentReturnType && !RetValExp->isTypeDependent()) { // we have a non-void function with an expression, continue checking InitializedEntity Entity = InitializedEntity::InitializeResult(ReturnLoc, Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -741,7 +741,7 @@ if (D->isNRVOVariable()) { QualType ReturnType = cast(DC)->getReturnType(); - if (SemaRef.isCopyElisionCandidate(ReturnType, Var, false)) + if (SemaRef.isCopyElisionCandidate(ReturnType, Var, Sema::CES_Strict)) Var->setNRVOVariable(true); } Index: test/SemaCXX/warn-return-std-move.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-return-std-move.cpp @@ -0,0 +1,262 @@ +// RUN: %clang_cc1 -fsyntax-only -Wreturn-std-move -Wreturn-std-move-in-cxx11 -std=c++11 -verify %s + +// definitions for std::move +namespace std { +inline namespace foo { +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template typename remove_reference::type &&move(T &&t); +} +} + +struct Instrument { + Instrument() {} + Instrument(Instrument&&) { /* MOVE */ } + Instrument(const Instrument&) { /* COPY */ } +}; +struct ConvertFromBase { Instrument i; }; +struct ConvertFromDerived { Instrument i; }; +struct Base { + Instrument i; + operator ConvertFromBase() const& { return ConvertFromBase{i}; } + operator ConvertFromBase() && { return ConvertFromBase{std::move(i)}; } +}; +struct Derived : public Base { + operator ConvertFromDerived() const& { return ConvertFromDerived{i}; } + operator ConvertFromDerived() && { return ConvertFromDerived{std::move(i)}; } +}; +struct ConstructFromBase { + Instrument i; + ConstructFromBase(const Base& b): i(b.i) {} + ConstructFromBase(Base&& b): i(std::move(b.i)) {} +}; +struct ConstructFromDerived { + Instrument i; + ConstructFromDerived(const Derived& d): i(d.i) {} + ConstructFromDerived(Derived&& d): i(std::move(d.i)) {} +}; + +struct TrivialInstrument { + int i = 42; +}; +struct ConvertFromTrivialBase { TrivialInstrument i; }; +struct ConvertFromTrivialDerived { TrivialInstrument i; }; +struct TrivialBase { + TrivialInstrument i; + operator ConvertFromTrivialBase() const& { return ConvertFromTrivialBase{i}; } + operator ConvertFromTrivialBase() && { return ConvertFromTrivialBase{std::move(i)}; } +}; +struct TrivialDerived : public TrivialBase { + operator ConvertFromTrivialDerived() const& { return ConvertFromTrivialDerived{i}; } + operator ConvertFromTrivialDerived() && { return ConvertFromTrivialDerived{std::move(i)}; } +}; +struct ConstructFromTrivialBase { + TrivialInstrument i; + ConstructFromTrivialBase(const TrivialBase& b): i(b.i) {} + ConstructFromTrivialBase(TrivialBase&& b): i(std::move(b.i)) {} +}; +struct ConstructFromTrivialDerived { + TrivialInstrument i; + ConstructFromTrivialDerived(const TrivialDerived& d): i(d.i) {} + ConstructFromTrivialDerived(TrivialDerived&& d): i(std::move(d.i)) {} +}; + +Derived test1() { + Derived d1; + return d1; // ok +} +Base test2() { + Derived d2; + return d2; // e1 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConstructFromDerived test3() { + Derived d3; + return d3; // e2-cxx11 + // expected-warning@-1{{would have selected a better conversion sequence}} +} +ConstructFromBase test4() { + Derived d4; + return d4; // e3 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConvertFromDerived test5() { + Derived d5; + return d5; // e4 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConvertFromBase test6() { + Derived d6; + return d6; // e5 + // expected-warning@-1{{would select a better conversion sequence}} +} + +// These test cases should not produce the warning. +Derived ok1() { Derived d; return d; } +Base ok2() { Derived d; return static_cast(d); } +ConstructFromDerived ok3() { Derived d; return static_cast(d); } +ConstructFromBase ok4() { Derived d; return static_cast(d); } +ConvertFromDerived ok5() { Derived d; return static_cast(d); } +ConvertFromBase ok6() { Derived d; return static_cast(d); } + +// If the target is an lvalue reference, assume it's not safe to move from. +Derived ok_lvalue1(Derived& d) { return d; } +Base ok_lvalue2(Derived& d) { return d; } +ConstructFromDerived ok_lvalue3(Derived& d) { return d; } +ConstructFromBase ok_lvalue4(Derived& d) { return d; } +ConvertFromDerived ok_lvalue5(Derived& d) { return d; } +ConvertFromBase ok_lvalue6(Derived& d) { return d; } + +// If the target is a global, assume it's not safe to move from. +static Derived global_d; +Derived ok_global1() { return global_d; } +Base ok_global2() { return global_d; } +ConstructFromDerived ok_global3() { return global_d; } +ConstructFromBase ok_global4() { return global_d; } +ConvertFromDerived ok_global5() { return global_d; } +ConvertFromBase ok_global6() { return global_d; } + +// If the target's copy constructor is trivial, assume the programmer doesn't care. +TrivialDerived ok_trivial1(TrivialDerived d) { return d; } +TrivialBase ok_trivial2(TrivialDerived d) { return d; } +ConstructFromTrivialDerived ok_trivial3(TrivialDerived d) { return d; } +ConstructFromTrivialBase ok_trivial4(TrivialDerived d) { return d; } +ConvertFromTrivialDerived ok_trivial5(TrivialDerived d) { return d; } +ConvertFromTrivialBase ok_trivial6(TrivialDerived d) { return d; } + +// If the target is a parameter, do apply the diagnostic. +Derived testParam1(Derived d) { return d; } +Base testParam2(Derived d) { + return d; // e6 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConstructFromDerived testParam3(Derived d) { + return d; // e7-cxx11 + // expected-warning@-1{{would have selected a better conversion sequence}} +} +ConstructFromBase testParam4(Derived d) { + return d; // e8 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConvertFromDerived testParam5(Derived d) { + return d; // e9 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConvertFromBase testParam6(Derived d) { + return d; // e10 + // expected-warning@-1{{would select a better conversion sequence}} +} + +// If the target is an rvalue reference parameter, do apply the diagnostic. +Derived testRParam1(Derived&& d) { + return d; // e11 + // expected-warning@-1{{would select a better conversion sequence}} +} +Base testRParam2(Derived&& d) { + return d; // e12 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConstructFromDerived testRParam3(Derived&& d) { + return d; // e13 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConstructFromBase testRParam4(Derived&& d) { + return d; // e14 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConvertFromDerived testRParam5(Derived&& d) { + return d; // e15 + // expected-warning@-1{{would select a better conversion sequence}} +} +ConvertFromBase testRParam6(Derived&& d) { + return d; // e16 + // expected-warning@-1{{would select a better conversion sequence}} +} + +// If the target is a catch-handler parameter, do apply the diagnostic. +void throw_derived(); +Derived testEParam1() { + try { throw_derived(); } catch (Derived d) { return d; } // e17 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +Base testEParam2() { + try { throw_derived(); } catch (Derived d) { return d; } // e18 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConstructFromDerived testEParam3() { + try { throw_derived(); } catch (Derived d) { return d; } // e19 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConstructFromBase testEParam4() { + try { throw_derived(); } catch (Derived d) { return d; } // e20 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConvertFromDerived testEParam5() { + try { throw_derived(); } catch (Derived d) { return d; } // e21 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConvertFromBase testEParam6() { + try { throw_derived(); } catch (Derived d) { return d; } // e22 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} + +// Even if the exception variable is an lvalue reference, we still own it. +Derived testREParam1() { + try { throw_derived(); } catch (Derived& d) { return d; } // e23 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +Base testREParam2() { + try { throw_derived(); } catch (Derived& d) { return d; } // e24 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConstructFromDerived testREParam3() { + try { throw_derived(); } catch (Derived& d) { return d; } // e25 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConstructFromBase testREParam4() { + try { throw_derived(); } catch (Derived& d) { return d; } // e26 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConvertFromDerived testREParam5() { + try { throw_derived(); } catch (Derived& d) { return d; } // e27 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} +ConvertFromBase testREParam6() { + try { throw_derived(); } catch (Derived& d) { return d; } // e28 + // expected-warning@-1{{would select a better conversion sequence}} + __builtin_unreachable(); +} + +Derived ok_CEParam1() { try { throw_derived(); } catch (const Derived& d) { return d; } __builtin_unreachable(); } +Base ok_CEParam2() { try { throw_derived(); } catch (const Derived& d) { return d; } __builtin_unreachable(); } +ConstructFromDerived ok_CEParam3() { try { throw_derived(); } catch (const Derived& d) { return d; } __builtin_unreachable(); } +ConstructFromBase ok_CEParam4() { try { throw_derived(); } catch (const Derived& d) { return d; } __builtin_unreachable(); } +ConvertFromDerived ok_CEParam5() { try { throw_derived(); } catch (const Derived& d) { return d; } __builtin_unreachable(); } +ConvertFromBase ok_CEParam6() { try { throw_derived(); } catch (const Derived& d) { return d; } __builtin_unreachable(); } + + +// If rvalue overload resolution would find a copy constructor anyway, +// or if the copy constructor actually selected is trivial, then don't warn. +struct TriviallyCopyable {}; +struct OnlyCopyable { + OnlyCopyable() = default; + OnlyCopyable(const OnlyCopyable&) {} +}; + +TriviallyCopyable ok_copy1() { TriviallyCopyable c; return c; } +OnlyCopyable ok_copy2() { OnlyCopyable c; return c; } +TriviallyCopyable ok_copyparam1(TriviallyCopyable c) { return c; } +OnlyCopyable ok_copyparam2(OnlyCopyable c) { return c; }