What Is Copy/Move Constructor Choosing Rule in C++? When Does Move-To-Copy Fallback Happen

What is copy/move constructor choosing rule in C++? When does move-to-copy fallback happen?

There is no "fallback". It is called overload resolution. If there are more than one possible candidate in overload resolution then the best match is chosen, according to a complicated set of rules which you can find by reading the C++ standard or a draft of it.

Here is an example without constructors.

class X { };

void func(X &&) { cout << "move\n"; } // 1
void func(X const &) { cout << "copy\n"; } // 2

int main()
{
func( X{} );
}
  • As-is: prints "move"
  • Comment out "1": prints "copy"
  • Comment out "2": prints "move"
  • Comment out "1" and "2": fails to compile

In overload resolution, binding rvalue to rvalue has higher preference than lvalue to rvalue.


Here is a very similar example:

void func(int) { cout << "int\n"; }      // 1
void func(long) { cout << "long\n"; } // 2

int main()
{
func(1);
}
  • As-is: prints "int"
  • Comment out "1": prints "long"
  • Comment out "2": prints "int"
  • Comment out "1" and "2": fails to compile

In overload resolution, an exact match is preferred to a conversion.


In your three examples on this thread we have:

1: Two candidate functions; rvalue prefers rvalue (as in my first example)

A(const A&);
A(A&&); // chosen

2: Two candidate functions; rvalue prefers rvalue (as in my first example)

A(const A&); 
A(A&&); // chosen

3: One candidate function; no contest

A(const A&);      // implicitly declared, chosen

As explained earlier, there is no implicit declaration of A(A&&) in case 3 because you have a destructor.

For overload resolution it does not matter whether the function body exists or not, it is whether the function is declared (either explicitly or implicitly).

Fallback to copy constructor not working?

A function with a deleted definition is still declared. Among other things, it participates in overload resolution normally - but if the overload resolution actually selects it, the program is ill-formed ([dcl.fct.def.delete]/2):

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
[ Note: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member
to the function. It applies even for references in expressions that are not potentially-evaluated. If a function
is overloaded, it is referenced only if the function is selected by overload resolution. —end note ]

This is different from a function that was never declared at all. A declaration that does not exist does not, of course, participate in overload resolution.

Why does the compiler require a copying constructor, need and have moving one and doesn't uses any of them?

I think you are asking why this

A a[2] = { 0, 1 };

fails to compile, while you would expect it to compile because A may have a move constructor. But it doesn't.

The reason is that A has a member that is not copyable, so its own copy constructor is deleted, and this counts as a user declared copy constructor has a user-declared destructor.

This in turn means A has no implicitly declared move constructor. You have to enable move construction, which you can do by defaulting the constructor:

A(A&&) = default;

To check whether a class is move constructible, you can use is_move_constructible, from the type_traits header:

std::cout << std::boolalpha;
std::cout << std::is_move_constructible<A>::value << std::endl;

This outputs false in your case.

Why is the move constructor called in this case?

From cppreference.com for return [expression]; :

If [expression] is an lvalue expression that is the (possibly parenthesized) name of an automatic storage duration object declared in the body or as a parameter of the innermost enclosing function or lambda expression, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if [expression] were an rvalue expression (thus it may select the move constructor), and if no suitable conversion is available, 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 a second time, with [expression] considered as an lvalue (so it may select the copy constructor taking a reference to non-const).

In short, since C++11, a return statement will prefer to use the move constructor if possible and fallback to the copy constructor.

Class with a deleted copy constructor can be still copied?

In foo({ });, the parameter is copy list-initialized. As the result it's initialized by the default constructor of std::atomic.

In foo(atomic<int>{ });, the parameter is copy-initialized from the temporary std::atomic (i.e. atomic<int>{ }). Before C++17 even the copy/move operation might be elided the copy/move constructor still has to be present and accessible. Since C++17 this is not required again because of mandatory copy elision, it's guaranteed that the parameter is initialized by the default constructor of std::atomic directly.

First, if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object: see copy elision (since C++17)

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible: (since C++17)

When does NRVO kick in? What are the requirements to be satisfied?

Apparently there was a change in the C++20 standard that affects copy/move elision (Quoting the draft):

Affected subclause: [class.copy.elision]
Change: A function
returning an implicitly movable entity may invoke a constructor taking
an rvalue reference to a type different from that of the returned expression.
Function and catch-clause parameters can be thrown using move
constructors.

And the example that is given:

struct base {
base();
base(base const &);
private:
base(base &&);
};

struct derived : base {};

base f(base b) {
throw b; // error: base(base &&) is private
derived d;
return d; // error: base(base &&) is private
}

And the takeaway from [class.copy.elision] (emphasis mine):

An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference
to a non-volatile object type. In the following copy-initialization
contexts, a move operation is first considered before attempting a
copy operation:

If the expression in a return ([stmt.return])
or co_­return ([stmt.return.coroutine]) statement is a (possibly
parenthesized) id-expression that names an implicitly movable entity
declared in the body or parameter-declaration-clause of the innermost
enclosing function or lambda-expression, or

if the operand of
a throw-expression ([expr.throw]) is a (possibly parenthesized)
id-expression that names an implicitly movable entity that belongs to
a scope that does not contain the compound-statement of the innermost
try-block or function-try-block (if any) whose compound-statement or
ctor-initializer contains the throw-expression, overload resolution to
select the constructor for the copy or the return_­value overload to
call is first performed as if the expression or operand were an
rvalue
. If the first overload resolution fails or was not performed,
overload resolution is performed again, considering the expression or
operand as an lvalue
.

[Note 3: This two-stage overload resolution is performed regardless of
whether copy elision will occur
. It determines the constructor or the
return_­value overload to be called if elision is not performed, and
the selected constructor or return_­value overload must be accessible
even if the call is elided
. — end note]




Related Topics



Leave a reply



Submit