Why Is Copy Constructor Called Instead of Conversion Constructor

Why is copy constructor called instead of conversion constructor?

B b2 = a;

This is known as Copy Initialization.

It does the following:

  1. Create an object of type B from a by using B (const A& a).
  2. Copy the created temporary object to b2 by using B (const B& b).
  3. Destroy the temporary object by using ~B().

The error you get is not at step 1 but rather at step 2.

Where is this in the standard?

C++03 8.5 Initializers
Para 14:

....

— If the destination type is a (possibly cv-qualified) class type:

...

...

— Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

Why is the copy constructor called instead of the move constructor when returning?

Now I am returning this class via lvalue like this:

MyClass func()
{
return MyClass();
}

No, the returned expression is an xvalue (a kind of rvalue), used to initialise the result for return-by-value (things are a little more complicated since C++17, but this is still the gist of it; besides, you're on C++11).

In this case the move constructor gets called when returning the class object and everything works as expected.

Indeed; an rvalue will initialise an rvalue reference and thus the whole thing can match move constructors.

When I change the code above:

… now the expression is MyClass() << 5, which has type MyClass&. This is never an rvalue. It's an lvalue. It's an expression that refers to an existing object.

So, without an explicit std::move, that'll be used to copy-initialise the result. And, since your copy constructor is deleted, that can't work.


I'm surprised the example compiles at all, since a temporary can't be used to initialise an lvalue reference (your operator's first argument), though some toolchains (MSVS) are known to accept this as an extension.


then would return std::move(MyClass() << 5); work?

Yes, I believe so.

However that is very strange to look at, and makes the reader double-check to ensure there are no dangling references. This suggests there's a better way to accomplish this that results in clearer code:

MyClass func()
{
MyClass m;
m << 5;
return m;
}

Now you're still getting a move (because that's a special rule when returning local variables) without any strange antics. And, as a bonus, the << call is completely standard-compliant.

Why is template constructor preferred to copy constructor?

As far as I know non-template function is always preferred to template function during overload resolution.

This is true, only when the specialization and the non template are exactly the same. This is not the case here though. When you call uct u3(u1) The overload sets gets

uct(const uct &)
uct(uct &) // from the template

Now, since u1 is not const it would have to apply a const transformation to call the copy constructor. To call the template specialization it needs to do nothing since it is an exact match. That means the template wins as it is the better match.

To stop this one thing you can do is use SFINAE to limit the template function to only be called when T is not a uct. That would look like

template <typename T, std::enable_if_t<!std::is_same_v<uct, std::decay_t<T>>, bool> = true>
uct(T &&) { std::cerr << "template" << std::endl; }

Implicit conversions and copy constructors

B bar = foo is called copy-initialization: When the type of the right hand side doesn't match the type of the left hand side (in this case, a B vs A) the compiler creates a temporary initialized from the right hand side and then copies it using the copy-constructor. This is effectively your code:

B bar = B(foo);

Since you've removed const from your copy-constructor, you now get an error because you're trying to bind an rvalue to an lvalue reference. Rvalues can bind to const lvalue references as you've already seen.

This can be resolved by using direct-initialization. Now no copy is being created:

B bar(foo);

Why is forwarding reference constructor called instead of copy constructor?

I am guessing that it's because the move constructor was implicitly defined but not the copy constructor.

No, both are implicitly defined for class Something.

Why is the copy constructor being resolved to the templated forwarding reference constructor

Because the copy constructor takes const Something& as its parameter. That means for the copy constructor to be called, implicit conversion is needed for adding const qualifier. But the forwarding reference constructor could be instantiated to take Something& as its parameter, then it's an exact match and wins in the overload resolution.

So if you make something const, the implicitly defined copy constructor will be invoked for the 3rd case instead of the forwarding reference constructor.

LIVE

but not the move constructor?

Because for the move constructor the above issue doesn't matter. For the invocation of the 1st and 2nd case, both implicitly defined move constructor and forwarding reference constructor are exact match, then non-template move constructor wins.

Why is conversion constructor called in absense of assignment operator?

I want to know, in the absence of a user provided assignment operator, what happens when I do this num1 = something; ?

num1 = 12.5;

calls implicit Number::operator =(const Number&).

So constructs a temporary number from double.

Does the default assignment operator provided by the compiler get called or does a constructor get called?

Both. Constructor is called for the temporary, not the existing object.

What happens when the constructor is called on an object that already exists?
Does that object get "re-constructed"? So what actually happens in that case?

You cannot recall constructor on existing object. The nearest thing you can do is placement-new.

Conversion constructor called for move but not for copy

The rules to add implicitly generated (= compiler-generated) move constructors are pretty conservative: Only add one if it's safe. If a copy constructor has been defined by the user, the compiler cannot assume that adding some simple compiler-generated move constructor is still safe, so no move constructor will be added. Actually, it's enough to declare it to prevent the generation of the move constructor, IIRC to allow copy-only types.

Constructor templates are never considered neither as copy nor as move constructors, so they don't prevent the implicit generation of those.

Of course, if the move constructor is never declared, another constructor can be chosen by overload resolution instead. Consider this example:

#include <iostream>
#include <utility>

struct loud
{
loud() { std::cout << "default ctor\n"; }
loud(loud const&) { std::cout << "copy ctor\n"; }
loud(loud&&) { std::cout << "move ctor\n"; }
};

struct foo
{
loud l;
};

struct bar
{
loud l;
bar() = default;
bar(bar const&) = default;
};

int main()
{
foo f0;
foo f1(f0);
foo f2(std::move(f0));

std::cout << "--------------\n";

bar b0;
bar b1(b0);
bar b2(std::move(b0));
}

Output:

default ctor
copy ctor
move ctor
--------------
default ctor
copy ctor
copy ctor

Here, foo will get implicitly declared default, copy and move constructors, whereas bar won't get an implicitly declared move constructor, since it has user-declared copy constructor.


In your second case, there's no implicitly-declared move constructor, therefore overload resolution for Test<int> testInt4(std::move(testInt2)) prefers the constructor template to the copy constructor, since the former takes a reference that's less cv-qualified.


To reduce code duplication, you could delegate construction:

template <class T>
class Test {
T _temp;

struct tag {};

public:
Test() {
std::cout << "Test()" << std::endl;
};

Test(Test const& test)
: Test(test, tag{})
{}

Test(Test&& test)
: Test(std::move(test), tag{})
{}

template <class T2> Test(Test<T2> const &test, tag = {}) {
std::cout << "template <class T2> Test(Test<T2> const &test)" << std::endl;
};

template <class T2> Test(Test<T2> &&test, tag = {}) {
std::cout << "template <class T2> Test(Test<T2> &&test)" << std::endl;
};

};

By the way, you can also reduce some boilerplate by using a perfectly forwarding constructor template instead of two constructor templates:

Using this trait:

template<class T>
struct is_Test : std::false_type {};
template<class T>
struct is_Test<Test<T>> : std::true_type {};

You can define:

    template <class T2,
class = typename std::enable_if<is_Test<T2>::value>::type>
Test(T2&& test, tag = {}) {
std::cout << "template <class T2> Test(T2&& test)" << std::endl;
};

If we convert a value with one constructor, will there also be copy constructor needed to copy the newly created temp object?

As a first hint you can add a copy constructor that prints something to see if it gets called:

#include <iostream>

struct foo {
template <size_t n>
foo(const char(&str)[n]){
std::cout << "converting constructor\n";
}
foo(const foo& f){
std::cout << "copy constructor\n";
}
};

void bar(foo){}

int main() {
bar("asdf");
}

Output is:

converting constructor

No copy constructor is called in this example. This is only a hint, because it is the output with one specific compiler and one specific C++ standard. However, once a foo has been created by calling the converting constructor, there is no reason to call the copy constructor. The string literal "asdf" is converted to a foo and thats it. There is no additional copy in the code, hence no compiler should create another copy.

Why Copy Constructor is called here instead of normal Constructor and overloaded assignment operator?

Because C c4 = c1; is syntactically semantically equivalent to:

C c4(c1);

Both would invoke copy constructor in this particular case. However there is a subtle difference between "copy initialization" (1st syntax) and "direct initialization" (2nd syntax). Look at this answer.

Note: In "layman's terms", a variable (here c4) is constructed till the first ; (or ,' for multiple objects) is encountered; Till then everything is one or the other type of constructor.

In case of C c4 = c1;, compiler doesn't have to check for most vexing parse.

However one can disable C c4 = c1; kind of syntax by declaring copy constructor explicit. For that matter any constructor can be made explicit and you can prevent = sign in the construction.



Related Topics



Leave a reply



Submit