Skip to content

Commit

Permalink
[Sema] Implement C++14's DR1579: Prefer returning by converting move …
Browse files Browse the repository at this point in the history
…constructor

Fixes PR28096.

Differential Revision: http://reviews.llvm.org/D21619

llvm-svn: 274291
  • Loading branch information
epilk committed Jun 30, 2016
1 parent 0490cde commit fc235eb
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 50 deletions.
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Initialization.h
Expand Up @@ -952,6 +952,9 @@ class InitializationSequence {
step_iterator step_begin() const { return Steps.begin(); }
step_iterator step_end() const { return Steps.end(); }

typedef llvm::iterator_range<step_iterator> step_range;
step_range steps() const { return {step_begin(), step_end()}; }

/// \brief Determine whether this initialization is a direct reference
/// binding (C++ [dcl.init.ref]).
bool isDirectReferenceBinding() const;
Expand Down
4 changes: 2 additions & 2 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -3476,9 +3476,9 @@ class Sema {
SourceLocation Loc,
unsigned NumParams);
VarDecl *getCopyElisionCandidate(QualType ReturnType, Expr *E,
bool AllowFunctionParameters);
bool AllowParamOrMoveConstructible);
bool isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD,
bool AllowFunctionParameters);
bool AllowParamOrMoveConstructible);

StmtResult ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp,
Scope *CurScope);
Expand Down
91 changes: 51 additions & 40 deletions clang/lib/Sema/SemaStmt.cpp
Expand Up @@ -2697,16 +2697,16 @@ Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope) {
/// \param E The expression being returned from the function or block, or
/// being thrown.
///
/// \param AllowFunctionParameter Whether we allow function parameters to
/// be considered NRVO candidates. C++ prohibits this 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 does allow function parameters).
/// \param AllowParamOrMoveConstructible 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
/// does allow function parameters).
///
/// \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 AllowFunctionParameter) {
VarDecl *Sema::getCopyElisionCandidate(QualType ReturnType, Expr *E,
bool AllowParamOrMoveConstructible) {
if (!getLangOpts().CPlusPlus)
return nullptr;

Expand All @@ -2719,34 +2719,38 @@ VarDecl *Sema::getCopyElisionCandidate(QualType ReturnType,
if (!VD)
return nullptr;

if (isCopyElisionCandidate(ReturnType, VD, AllowFunctionParameter))
if (isCopyElisionCandidate(ReturnType, VD, AllowParamOrMoveConstructible))
return VD;
return nullptr;
}

bool Sema::isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD,
bool AllowFunctionParameter) {
bool AllowParamOrMoveConstructible) {
QualType VDType = VD->getType();
// - in a return statement in a function with ...
// ... a class return type ...
if (!ReturnType.isNull() && !ReturnType->isDependentType()) {
if (!ReturnType->isRecordType())
return false;
// ... the same cv-unqualified type as the function return type ...
if (!VDType->isDependentType() &&
// When considering moving this expression out, allow dissimilar types.
if (!AllowParamOrMoveConstructible && !VDType->isDependentType() &&
!Context.hasSameUnqualifiedType(ReturnType, VDType))
return false;
}

// ...object (other than a function or catch-clause parameter)...
if (VD->getKind() != Decl::Var &&
!(AllowFunctionParameter && VD->getKind() == Decl::ParmVar))
!(AllowParamOrMoveConstructible && VD->getKind() == Decl::ParmVar))
return false;
if (VD->isExceptionVariable()) return false;

// ...automatic...
if (!VD->hasLocalStorage()) return false;

if (AllowParamOrMoveConstructible)
return true;

// ...non-volatile...
if (VD->getType().isVolatileQualified()) return false;

Expand All @@ -2765,7 +2769,7 @@ bool Sema::isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD,
/// \brief Perform the initialization of a potentially-movable value, which
/// is the result of return value.
///
/// This routine implements C++0x [class.copy]p33, which attempts to treat
/// 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.
ExprResult
Expand All @@ -2774,52 +2778,59 @@ Sema::PerformMoveOrCopyInitialization(const InitializedEntity &Entity,
QualType ResultType,
Expr *Value,
bool AllowNRVO) {
// C++0x [class.copy]p33:
// When the criteria for elision of a copy operation are met or would
// be met save for the fact that the source object is a function
// parameter, and the object to be copied is designated by an lvalue,
// overload resolution to select the constructor for the copy is first
// performed as if the object were designated by an rvalue.
// 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 &&
(NRVOCandidate || getCopyElisionCandidate(ResultType, Value, true))) {
ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack,
Value->getType(), CK_NoOp, Value, VK_XValue);

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());
InitializationSequence Seq(*this, Entity, Kind, InitExpr);

// [...] If overload resolution fails, 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.
InitializationKind Kind = InitializationKind::CreateCopy(
Value->getLocStart(), Value->getLocStart());

InitializationSequence Seq(*this, Entity, Kind, InitExpr);
if (Seq) {
for (InitializationSequence::step_iterator Step = Seq.step_begin(),
StepEnd = Seq.step_end();
Step != StepEnd; ++Step) {
if (Step->Kind != InitializationSequence::SK_ConstructorInitialization)
for (const InitializationSequence::Step &Step : Seq.steps()) {
if (!(Step.Kind ==
InitializationSequence::SK_ConstructorInitialization ||
(Step.Kind == InitializationSequence::SK_UserConversion &&
isa<CXXConstructorDecl>(Step.Function.Function))))
continue;

CXXConstructorDecl *Constructor
= cast<CXXConstructorDecl>(Step->Function.Function);
CXXConstructorDecl *Constructor =
cast<CXXConstructorDecl>(Step.Function.Function);

const RValueReferenceType *RRefType
= Constructor->getParamDecl(0)->getType()
->getAs<RValueReferenceType>();

// If we don't meet the criteria, break out now.
// [...] 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(),
Context.getTypeDeclType(Constructor->getParent())))
NRVOCandidate->getType()))
break;

// 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);
Value = ImplicitCastExpr::Create(Context, Value->getType(), CK_NoOp,
Value, nullptr, VK_XValue);

// Complete type-checking the initialization of the return type
// using the constructor we found.
Expand Down
56 changes: 56 additions & 0 deletions clang/test/CXX/drs/dr15xx.cpp
Expand Up @@ -87,6 +87,62 @@ namespace std {

} // std

namespace dr1579 { // dr1579: 3.9
template<class T>
struct GenericMoveOnly {
GenericMoveOnly();
template<class U> GenericMoveOnly(const GenericMoveOnly<U> &) = delete; // expected-note 5 {{marked deleted here}}
GenericMoveOnly(const int &) = delete; // expected-note 2 {{marked deleted here}}
template<class U> GenericMoveOnly(GenericMoveOnly<U> &&);
GenericMoveOnly(int &&);
};

GenericMoveOnly<float> DR1579_Eligible(GenericMoveOnly<char> CharMO) {
int i;
GenericMoveOnly<char> GMO;

if (0)
return i;
else if (0)
return GMO;
else if (0)
return ((GMO));
else
return CharMO;
}

GenericMoveOnly<char> GlobalMO;

GenericMoveOnly<float> DR1579_Ineligible(int &AnInt,
GenericMoveOnly<char> &CharMO) {
static GenericMoveOnly<char> StaticMove;
extern GenericMoveOnly<char> ExternMove;

if (0)
return AnInt; // expected-error{{invokes a deleted function}}
else if (0)
return GlobalMO; // expected-error{{invokes a deleted function}}
else if (0)
return StaticMove; // expected-error{{invokes a deleted function}}
else if (0)
return ExternMove; // expected-error{{invokes a deleted function}}
else if (0)
return AnInt; // expected-error{{invokes a deleted function}}
else
return CharMO; // expected-error{{invokes a deleted function}}
}

auto DR1579_lambda_valid = [](GenericMoveOnly<float> mo) ->
GenericMoveOnly<char> {
return mo;
};

auto DR1579_lambda_invalid = []() -> GenericMoveOnly<char> {
static GenericMoveOnly<float> mo;
return mo; // expected-error{{invokes a deleted function}}
};
} // end namespace dr1579

namespace dr1589 { // dr1589: 3.7 c++11
// Ambiguous ranking of list-initialization sequences

Expand Down
10 changes: 2 additions & 8 deletions clang/test/SemaCXX/rval-references.cpp
Expand Up @@ -72,23 +72,17 @@ int&& should_not_warn(int&& i) { // But GCC 4.4 does
// Test the return dance. This also tests IsReturnCopyElidable.
struct MoveOnly {
MoveOnly();
MoveOnly(const MoveOnly&) = delete; // expected-note {{candidate constructor}} \
// expected-note 3{{explicitly marked deleted here}}
MoveOnly(MoveOnly&&); // expected-note {{candidate constructor}}
MoveOnly(int&&); // expected-note {{candidate constructor}}
MoveOnly(const MoveOnly&) = delete; // expected-note 3{{explicitly marked deleted here}}
};

MoveOnly gmo;
MoveOnly returningNonEligible() {
int i;
static MoveOnly mo;
MoveOnly &r = mo;
if (0) // Copy from global can't be elided
return gmo; // expected-error {{call to deleted constructor}}
else if (0) // Copy from local static can't be elided
return mo; // expected-error {{call to deleted constructor}}
else if (0) // Copy from reference can't be elided
else // Copy from reference can't be elided
return r; // expected-error {{call to deleted constructor}}
else // Construction from different type can't be elided
return i; // expected-error {{no viable conversion from returned value of type 'int' to function return type 'MoveOnly'}}
}

0 comments on commit fc235eb

Please sign in to comment.