[clang] C++98 implicit moves are back with a vengeance

After taking C++98 implicit moves out in D104500,
we put it back in, but now in a new form which preserves
compatibility with pure C++98 programs, while at the same time
giving almost all the goodies from P1825.

* We use the exact same rules as C++20 with regards to which
  id-expressions are move eligible. The previous
  incarnation would only benefit from the proper subset which is
  copy ellidable. This means we can implicit move, in addition:
  * Parameters.
  * RValue references.
  * Exception variables.
  * Variables with higher-than-natural required alignment.
  * Objects with different type from the function return type.
* We preserve the two-overload resolution, with one small tweak to the
  first one: If we either pick a (possibly converting) constructor which
  does not take an rvalue reference, or a user conversion operator which
  is not ref-qualified, we abort into the second overload resolution.

This gives C++98 almost all the implicit move patterns which we had created test
cases for, while at the same time preserving the meaning of these
three patterns, which are found in pure C++98 programs:
* Classes with both const and non-const copy constructors, but no move
  constructors, continue to have their non-const copy constructor
  selected.
* We continue to reject as ambiguous the following pattern:
```
struct A { A(B &); };
struct B { operator A(); };
A foo(B x) { return x; }
```
* We continue to pick the copy constructor in the following pattern:
```
class AutoPtrRef { };
struct AutoPtr {
  AutoPtr(AutoPtr &);
  AutoPtr();

  AutoPtr(AutoPtrRef);
  operator AutoPtrRef();
};
AutoPtr test_auto_ptr() {
  AutoPtr p;
  return p;
}
```

Signed-off-by: Matheus Izvekov <mizvekov@gmail.com>

Reviewed By: Quuxplusone

Differential Revision: https://reviews.llvm.org/D105756
This commit is contained in:
Matheus Izvekov 2021-07-10 02:34:17 +02:00
parent 14f77576c9
commit 03282f2fe1
4 changed files with 97 additions and 63 deletions

View File

@ -3451,6 +3451,28 @@ const VarDecl *Sema::getCopyElisionCandidate(NamedReturnInfo &Info,
return Info.isCopyElidable() ? Info.Candidate : nullptr;
}
/// Verify that the initialization sequence that was picked for the
/// first overload resolution is permissible under C++98.
///
/// Reject (possibly converting) contructors not taking an rvalue reference,
/// or user conversion operators which are not ref-qualified.
static bool
VerifyInitializationSequenceCXX98(const Sema &S,
const InitializationSequence &Seq) {
const auto *Step = llvm::find_if(Seq.steps(), [](const auto &Step) {
return Step.Kind == InitializationSequence::SK_ConstructorInitialization ||
Step.Kind == InitializationSequence::SK_UserConversion;
});
if (Step != Seq.step_end()) {
const auto *FD = Step->Function.Function;
if (isa<CXXConstructorDecl>(FD)
? !FD->getParamDecl(0)->getType()->isRValueReferenceType()
: cast<CXXMethodDecl>(FD)->getRefQualifier() == RQ_None)
return false;
}
return true;
}
/// Perform the initialization of a potentially-movable value, which
/// is the result of return value.
///
@ -3461,8 +3483,7 @@ ExprResult
Sema::PerformMoveOrCopyInitialization(const InitializedEntity &Entity,
const NamedReturnInfo &NRInfo,
Expr *Value) {
if (getLangOpts().CPlusPlus11 && !getLangOpts().CPlusPlus2b &&
NRInfo.isMoveEligible()) {
if (!getLangOpts().CPlusPlus2b && NRInfo.isMoveEligible()) {
ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack, Value->getType(),
CK_NoOp, Value, VK_XValue, FPOptionsOverride());
Expr *InitExpr = &AsRvalue;
@ -3470,7 +3491,9 @@ Sema::PerformMoveOrCopyInitialization(const InitializedEntity &Entity,
Value->getBeginLoc());
InitializationSequence Seq(*this, Entity, Kind, InitExpr);
auto Res = Seq.getFailedOverloadResult();
if (Res == OR_Success || Res == OR_Deleted) {
if ((Res == OR_Success || Res == OR_Deleted) &&
(getLangOpts().CPlusPlus11 ||
VerifyInitializationSequenceCXX98(*this, Seq))) {
// Promote "AsRvalue" to the heap, since we now need this
// expression node to persist.
Value =

View File

@ -7,11 +7,11 @@ namespace test_delete_function {
struct A1 {
A1();
A1(const A1 &);
A1(A1 &&) = delete; // cxx11_2b-note {{'A1' has been explicitly marked deleted here}}
A1(A1 &&) = delete; // expected-note {{'A1' has been explicitly marked deleted here}}
};
A1 test1() {
A1 a;
return a; // cxx11_2b-error {{call to deleted constructor of 'test_delete_function::A1'}}
return a; // expected-error {{call to deleted constructor of 'test_delete_function::A1'}}
}
struct A2 {
@ -19,33 +19,33 @@ struct A2 {
A2(const A2 &);
private:
A2(A2 &&); // cxx11_2b-note {{declared private here}}
A2(A2 &&); // expected-note {{declared private here}}
};
A2 test2() {
A2 a;
return a; // cxx11_2b-error {{calling a private constructor of class 'test_delete_function::A2'}}
return a; // expected-error {{calling a private constructor of class 'test_delete_function::A2'}}
}
struct C {};
struct B1 {
B1(C &);
B1(C &&) = delete; // cxx11_2b-note {{'B1' has been explicitly marked deleted here}}
B1(C &&) = delete; // expected-note {{'B1' has been explicitly marked deleted here}}
};
B1 test3() {
C c;
return c; // cxx11_2b-error {{conversion function from 'test_delete_function::C' to 'test_delete_function::B1' invokes a deleted function}}
return c; // expected-error {{conversion function from 'test_delete_function::C' to 'test_delete_function::B1' invokes a deleted function}}
}
struct B2 {
B2(C &);
private:
B2(C &&); // cxx11_2b-note {{declared private here}}
B2(C &&); // expected-note {{declared private here}}
};
B2 test4() {
C c;
return c; // cxx11_2b-error {{calling a private constructor of class 'test_delete_function::B2'}}
return c; // expected-error {{calling a private constructor of class 'test_delete_function::B2'}}
}
} // namespace test_delete_function
@ -54,38 +54,38 @@ B2 test4() {
namespace test_implicitly_movable_rvalue_ref {
struct A1 {
A1(A1 &&);
A1(const A1 &) = delete; // cxx98-note {{marked deleted here}}
A1(const A1 &) = delete;
};
A1 test1(A1 &&a) {
return a; // cxx98-error {{call to deleted constructor}}
return a;
}
struct A2 {
A2(A2 &&);
private:
A2(const A2 &); // cxx98-note {{declared private here}}
A2(const A2 &);
};
A2 test2(A2 &&a) {
return a; // cxx98-error {{calling a private constructor}}
return a;
}
struct B1 {
B1(const B1 &);
B1(B1 &&) = delete; // cxx11_2b-note {{'B1' has been explicitly marked deleted here}}
B1(B1 &&) = delete; // expected-note {{'B1' has been explicitly marked deleted here}}
};
B1 test3(B1 &&b) {
return b; // cxx11_2b-error {{call to deleted constructor of 'test_implicitly_movable_rvalue_ref::B1'}}
return b; // expected-error {{call to deleted constructor of 'test_implicitly_movable_rvalue_ref::B1'}}
}
struct B2 {
B2(const B2 &);
private:
B2(B2 &&); // cxx11_2b-note {{declared private here}}
B2(B2 &&); // expected-note {{declared private here}}
};
B2 test4(B2 &&b) {
return b; // cxx11_2b-error {{calling a private constructor of class 'test_implicitly_movable_rvalue_ref::B2'}}
return b; // expected-error {{calling a private constructor of class 'test_implicitly_movable_rvalue_ref::B2'}}
}
} // namespace test_implicitly_movable_rvalue_ref
@ -96,13 +96,13 @@ void func();
struct A1 {
A1(const A1 &);
A1(A1 &&) = delete; // cxx11_2b-note 2{{'A1' has been explicitly marked deleted here}}
A1(A1 &&) = delete; // expected-note 2{{'A1' has been explicitly marked deleted here}}
};
void test1() {
try {
func();
} catch (A1 a) {
throw a; // cxx11_2b-error {{call to deleted constructor of 'test_throw_parameter::A1'}}
throw a; // expected-error {{call to deleted constructor of 'test_throw_parameter::A1'}}
}
}
@ -110,20 +110,20 @@ struct A2 {
A2(const A2 &);
private:
A2(A2 &&); // cxx11_2b-note {{declared private here}}
A2(A2 &&); // expected-note {{declared private here}}
};
void test2() {
try {
func();
} catch (A2 a) {
throw a; // cxx11_2b-error {{calling a private constructor of class 'test_throw_parameter::A2'}}
throw a; // expected-error {{calling a private constructor of class 'test_throw_parameter::A2'}}
}
}
void test3(A1 a) try {
func();
} catch (...) {
throw a; // cxx11_2b-error {{call to deleted constructor of 'test_throw_parameter::A1'}}
throw a; // expected-error {{call to deleted constructor of 'test_throw_parameter::A1'}}
}
} // namespace test_throw_parameter
@ -134,42 +134,42 @@ class C {};
struct A1 {
operator C() &&;
operator C() const & = delete; // cxx98-note {{marked deleted here}}
operator C() const & = delete;
};
C test1() {
A1 a;
return a; // cxx98-error {{invokes a deleted function}}
return a;
}
struct A2 {
operator C() &&;
private:
operator C() const &; // cxx98-note {{declared private here}}
operator C() const &;
};
C test2() {
A2 a;
return a; // cxx98-error {{'operator C' is a private member}}
return a;
}
struct B1 {
operator C() const &;
operator C() && = delete; // cxx11_2b-note {{'operator C' has been explicitly marked deleted here}}
operator C() && = delete; // expected-note {{'operator C' has been explicitly marked deleted here}}
};
C test3() {
B1 b;
return b; // cxx11_2b-error {{conversion function from 'test_non_ctor_conversion::B1' to 'test_non_ctor_conversion::C' invokes a deleted function}}
return b; // expected-error {{conversion function from 'test_non_ctor_conversion::B1' to 'test_non_ctor_conversion::C' invokes a deleted function}}
}
struct B2 {
operator C() const &;
private:
operator C() &&; // cxx11_2b-note {{declared private here}}
operator C() &&; // expected-note {{declared private here}}
};
C test4() {
B2 b;
return b; // cxx11_2b-error {{'operator C' is a private member of 'test_non_ctor_conversion::B2'}}
return b; // expected-error {{'operator C' is a private member of 'test_non_ctor_conversion::B2'}}
}
} // namespace test_non_ctor_conversion
@ -197,7 +197,7 @@ struct NeedValue {
struct A1 {
A1();
A1(A1 &&);
A1(const A1 &) = delete; // cxx98-note 3{{marked deleted here}}
A1(const A1 &) = delete; // cxx98-note 2 {{marked deleted here}}
};
NeedValue test_1_1() {
// not rvalue reference
@ -210,7 +210,7 @@ A1 test_1_2() {
// rvalue reference
// not same type
DerivedA1 a;
return a; // cxx98-error {{call to deleted constructor}}
return a;
}
NeedValue test_1_3() {
// not rvalue reference
@ -224,7 +224,7 @@ struct A2 {
A2(A2 &&);
private:
A2(const A2 &); // cxx98-note 3{{declared private here}}
A2(const A2 &); // cxx98-note 2 {{declared private here}}
};
NeedValue test_2_1() {
// not rvalue reference
@ -237,7 +237,7 @@ A2 test_2_2() {
// rvalue reference
// not same type
DerivedA2 a;
return a; // cxx98-error {{calling a private constructor}}
return a;
}
NeedValue test_2_3() {
// not rvalue reference
@ -250,6 +250,7 @@ struct B1 {
B1();
B1(const B1 &);
B1(B1 &&) = delete; // cxx11_2b-note 3 {{'B1' has been explicitly marked deleted here}}
// cxx98-note@-1 {{'B1' has been explicitly marked deleted here}}
};
NeedValue test_3_1() {
// not rvalue reference
@ -262,7 +263,7 @@ B1 test_3_2() {
// rvalue reference
// not same type
DerivedB1 b;
return b; // cxx11_2b-error {{call to deleted constructor of 'test_ctor_param_rvalue_ref::B1'}}
return b; // expected-error {{call to deleted constructor of 'test_ctor_param_rvalue_ref::B1'}}
}
NeedValue test_3_3() {
// not rvalue reference
@ -277,6 +278,7 @@ struct B2 {
private:
B2(B2 &&); // cxx11_2b-note 3 {{declared private here}}
// cxx98-note@-1 {{declared private here}}
};
NeedValue test_4_1() {
// not rvalue reference
@ -289,7 +291,7 @@ B2 test_4_2() {
// rvalue reference
// not same type
DerivedB2 b;
return b; // cxx11_2b-error {{calling a private constructor of class 'test_ctor_param_rvalue_ref::B2'}}
return b; // expected-error {{calling a private constructor of class 'test_ctor_param_rvalue_ref::B2'}}
}
NeedValue test_4_3() {
// not rvalue reference
@ -302,20 +304,19 @@ NeedValue test_4_3() {
namespace test_lvalue_ref_is_not_moved_from {
struct Target {};
// cxx11_2b-note@-1 {{candidate constructor (the implicit copy constructor) not viable}}
// cxx98-note@-2 2{{candidate constructor (the implicit copy constructor) not viable}}
// cxx11_2b-note@-3 {{candidate constructor (the implicit move constructor) not viable}}
// expected-note@-1 {{candidate constructor (the implicit copy constructor) not viable}}
// cxx11_2b-note@-2 {{candidate constructor (the implicit move constructor) not viable}}
struct CopyOnly {
CopyOnly(CopyOnly &&) = delete; // cxx11_2b-note {{has been explicitly marked deleted here}}
CopyOnly(CopyOnly &&) = delete; // expected-note {{has been explicitly marked deleted here}}
CopyOnly(CopyOnly&);
operator Target() && = delete; // cxx11_2b-note {{has been explicitly marked deleted here}}
operator Target() && = delete; // expected-note {{has been explicitly marked deleted here}}
operator Target() &;
};
struct MoveOnly {
MoveOnly(MoveOnly &&); // cxx11_2b-note {{copy constructor is implicitly deleted because}}
operator Target() &&; // expected-note {{candidate function not viable}} cxx98-note {{candidate function not viable}}
operator Target() &&; // expected-note {{candidate function not viable}}
};
extern CopyOnly copyonly;
@ -328,7 +329,7 @@ CopyOnly t1() {
CopyOnly t2() {
CopyOnly&& r = static_cast<CopyOnly&&>(copyonly);
return r; // cxx11_2b-error {{call to deleted constructor}}
return r; // expected-error {{call to deleted constructor}}
}
MoveOnly t3() {
@ -348,7 +349,7 @@ Target t5() {
Target t6() {
CopyOnly&& r = static_cast<CopyOnly&&>(copyonly);
return r; // cxx11_2b-error {{invokes a deleted function}}
return r; // expected-error {{invokes a deleted function}}
}
Target t7() {
@ -358,7 +359,7 @@ Target t7() {
Target t8() {
MoveOnly&& r = static_cast<MoveOnly&&>(moveonly);
return r; // cxx98-error {{no viable conversion}}
return r;
}
} // namespace test_lvalue_ref_is_not_moved_from
@ -400,6 +401,10 @@ Target t4() {
} // namespace test_rvalue_ref_to_nonobject
// Both tests in test_constandnonconstcopy, and also test_conversion::test1, are
// "pure" C++98 tests (pretend 'delete' means 'private').
// However we may extend implicit moves into C++98, we must make sure the
// results in these are not changed.
namespace test_constandnonconstcopy {
struct ConstCopyOnly {
ConstCopyOnly();
@ -437,9 +442,9 @@ A test1(B x) { return x; } // cxx98-error-re {{conversion {{.*}} is ambiguous}}
struct C {};
struct D {
operator C() &;
operator C() const & = delete; // cxx11_2b-note {{marked deleted here}}
operator C() const & = delete; // expected-note {{marked deleted here}}
};
C test2(D x) { return x; } // cxx11_2b-error {{invokes a deleted function}}
C test2(D x) { return x; } // expected-error {{invokes a deleted function}}
} // namespace test_conversion

View File

@ -120,7 +120,9 @@ void f(Yb& a) {
char ch = a; // OK. calls Yb::operator char();
}
// Test conversion + copy construction.
// Test conversion + copy construction. This is a pure C++98 test.
// However we may extend implicit moves into C++98, we must make sure the
// result here is not changed.
class AutoPtrRef { };
class AutoPtr {

View File

@ -14,6 +14,10 @@ struct CopyOnly {
};
TEST(CopyOnly); // cxx2b-error {{no matching constructor}}
// Both ConstCopyOnly and NonConstCopyOnly are
// "pure" C++98 tests (pretend 'delete' means 'private').
// However we may extend implicit moves into C++98, we must make sure the
// results in these are not changed.
struct ConstCopyOnly {
ConstCopyOnly();
ConstCopyOnly(ConstCopyOnly &) = delete; // cxx98-note {{marked deleted here}}
@ -31,51 +35,51 @@ TEST(NonConstCopyOnly); // cxx11_2b-error {{call to deleted constructor}}
struct CopyNoMove {
CopyNoMove();
CopyNoMove(CopyNoMove &);
CopyNoMove(CopyNoMove &&) = delete; // cxx11_2b-note {{marked deleted here}}
CopyNoMove(CopyNoMove &&) = delete; // cxx98_2b-note {{marked deleted here}}
};
TEST(CopyNoMove); // cxx11_2b-error {{call to deleted constructor}}
TEST(CopyNoMove); // cxx98_2b-error {{call to deleted constructor}}
struct MoveOnly {
MoveOnly();
MoveOnly(MoveOnly &) = delete; // cxx98-note {{marked deleted here}}
MoveOnly(MoveOnly &) = delete;
MoveOnly(MoveOnly &&);
};
TEST(MoveOnly); // cxx98-error {{call to deleted constructor}}
TEST(MoveOnly);
struct NoCopyNoMove {
NoCopyNoMove();
NoCopyNoMove(NoCopyNoMove &) = delete; // cxx98-note {{marked deleted here}}
NoCopyNoMove(NoCopyNoMove &&) = delete; // cxx11_2b-note {{marked deleted here}}
NoCopyNoMove(NoCopyNoMove &) = delete;
NoCopyNoMove(NoCopyNoMove &&) = delete; // cxx98_2b-note {{marked deleted here}}
};
TEST(NoCopyNoMove); // cxx98_2b-error {{call to deleted constructor}}
struct ConvertingRVRef {
ConvertingRVRef();
ConvertingRVRef(ConvertingRVRef &) = delete; // cxx98-note {{marked deleted here}}
ConvertingRVRef(ConvertingRVRef &) = delete;
struct X {};
ConvertingRVRef(X &&);
operator X() const & = delete;
operator X() &&;
};
TEST(ConvertingRVRef); // cxx98-error {{call to deleted constructor}}
TEST(ConvertingRVRef);
struct ConvertingCLVRef {
ConvertingCLVRef();
ConvertingCLVRef(ConvertingCLVRef &);
struct X {};
ConvertingCLVRef(X &&); // cxx11_2b-note {{passing argument to parameter here}}
ConvertingCLVRef(X &&); // cxx98_2b-note {{passing argument to parameter here}}
operator X() const &;
operator X() && = delete; // cxx11_2b-note {{marked deleted here}}
operator X() && = delete; // cxx98_2b-note {{marked deleted here}}
};
TEST(ConvertingCLVRef); // cxx11_2b-error {{invokes a deleted function}}
TEST(ConvertingCLVRef); // cxx98_2b-error {{invokes a deleted function}}
struct SubSubMove {};
struct SubMove : SubSubMove {
SubMove();
SubMove(SubMove &) = delete; // cxx98-note {{marked deleted here}}
SubMove(SubMove &) = delete;
SubMove(SubSubMove &&);
};
TEST(SubMove); // cxx98-error {{call to deleted constructor}}
TEST(SubMove);