Question

Trying to compile the following code:

struct Foo
{
    explicit Foo ( void ) { }
    explicit Foo ( Foo&& rhs ) { }
};

Foo bar ( void )
{
    return Foo();
}

Getting the following error:

call to implicitly-deleted copy constructor of 'Foo'

Well, it's quite obvious that the copy-ctor is implicitly deleted.

Question 1: Why does the compiler need the copy-ctor of Foo? I expected the return value of bar to be constructed from the rvalue Foo() with move-ctor.

Then I redeclare the move-ctor as implicit and everything compiles successfully.

Question 2: Why the compiler does not need copy-ctor any more when I redeclare the move-ctor as implicit?

Question 3: What does explicit keyword mean in the context of copy and move ctors as it definitely means something different from from the context of regular ctors.

Was it helpful?

Solution

It is because returning a value is considered an implicit conversion.

Quoting from the C++11 standard:

6.6.3 The return statement

2 [...]

A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The value of the expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy or move of a temporary object (12.2). [...]

The conversion from the return expression to the temporary object to hold the return value is implicit. So just as this would result in an error

Foo f = Foo();   // Copy initialization, which means implicit conversion

having code as your example would also trigger a similar one.


Question 1: Why does the compiler need the copy-ctor of Foo? I expected the return value of bar to be constructed from the rvalue Foo() with move-ctor.

It is because Foo(Foo&&) is not a viable overload, because of the reasons above. The rules state that whenever the move constructor cannot be used, it is when the compiler shall then consider the copy constructor, which, in your case, is implicitly deleted due to the presence of a user-defined move constructor.

Question 2: Why the compiler does not need copy-ctor any more when I redeclare the move-ctor as implicit?

It is because your move constructor can be used now. Thus, the compiler can immediately use it without even considering the presence of the copy constructor.

Question 3: What does explicit keyword mean in the context of copy and move ctors as it definitely means something different from from the context of regular ctors.

IMHO, it doesn't make sense, and it only leads to problems, just as in your case.

OTHER TIPS

The return type of bar is Foo. There is no copy constructor, and an explicit move constructor cannot work because an implicit conversion between Foo&& and Foo is still required. In this sense, explicit Foo(Foo&&) is no different to any other explicit converting constructor. The problem wold still occur with a conversion from a different type. This is an analogous example using int:

struct Foo
{
    explicit Foo(int) {}
};

Foo bar ( void )
{
    return 42; // error: could not convert '42' from 'int' to 'Foo'
}

Q1: Because there is nothing else it can use.

Q2: Because it uses the move constructor to implicitly convert from Foo&& to Foo.

Q3: It means the same as it does with plain converting constructors.

This has to do with how overload resolution works in C++.

The first step of overload resolution is to form a set of candidate functions. The second step is to narrow the set of candidate functions down to a set of viable functions. The third step is to select the unique best viable function, if there is one. If the best viable function is deleted, then the program is ill-formed.

Because the move constructor is declared explicit, it is not a candidate function for the implicit conversion of Foo() to the return type of the function. The only candidate function is Foo::Foo(const Foo&), the implicitly declared copy constructor. This is a candidate function even though it is declared as deleted. It is selected by overload resolution, being the only viable function; and then you get an error for trying to call a deleted function.

If you don't declare the move constructor explicit, then both the move constructor and the implicitly declared copy constructor are candidate functions. Both will also be viable in this case. However, the move constructor wins overload resolution because rvalues prefer to bind to rvalue references over const lvalue references. Therefore the move constructor is the best viable function. In this case it doesn't matter that the copy constructor is deleted, because it loses in overload resolution.

tl;dr:

Answer 1: Because the move constructor is not a candidate function for the conversion.

Answer 2: Because the move constructor is a candidate function, and wins overload resolution.

Answer 3: Nope, it means the same thing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top