Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template Constructor Taking Precedence Over Normal Copy and Move Constructor?

Tags:

c++

c++11

The output of the following program...

#include <iostream>

using namespace std;

struct X
{
    X(const X&)              { cout << "copy" << endl; }
    X(X&&)                   { cout << "move" << endl; }

    template<class T> X(T&&) { cout << "tmpl" << endl; }
};

int main()
{
    X x1 = 42;
    X x2(x1);
}

is

tmpl
tmpl

The desired output is:

tmpl
copy

Why doesn't the concrete copy constructor take precedence over the template constructor?

Is there anyway to fix it so that the copy and move constructor overloads will take precedence over the template constructor?

like image 890
Andrew Tomazos Avatar asked Dec 31 '12 10:12

Andrew Tomazos


People also ask

What is the difference between a move constructor and a copy constructor?

Move constructor moves the resources in the heap, i.e., unlike copy constructors which copy the data of the existing object and assigning it to the new object move constructor just makes the pointer of the declared object to point to the data of temporary object and nulls out the pointer of the temporary objects.

Are move constructor automatically generated?

If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.

Why do we use move constructor?

A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.

Does copy constructor call default constructor?

The answer is No. The creation of the object memory is done via the new instruction. Copy constructor is then in charge of the actual copying (relevant only when it's not a shallow copy, obviously). You can, if you want, explicitly call a different constructor prior to the copy constructor execution.


2 Answers

Well, it is because of reference-collapsing.

At the overload resolution stage, when the function template is instantiated, T is deduded to be X&, so T&& (which is X& &&) becomes X& due to reference-collapsing, and the instantiated function from the function template becomes exact match and the copy-constructor requires a conversion from X& to const X& (which is why it is not selected being inferior match).

However, if you remove the const from the copy-constructor, the copy-constructor will be preferred. Try this:

X(/*const*/ X&) { cout << "copy" << endl; }

Output as expected.

Or alternatively, if you make the parameter in the function template as const T& then copy-constructor will be called (even if it remains same!), because reference-collapsing will not come into picture now:

template<class T> X(const T &) { cout << "tmpl" << endl; }

Output is expected, again.

like image 113
Nawaz Avatar answered Oct 18 '22 10:10

Nawaz


If you don't want to add another constructor (as other answers suggested), you can use SFINAE to constrain the call, by replacing your template constructor by this:

template<class T
    , typename std::enable_if<not std::is_same<X, typename std::decay<T>::type>::value, int>::type = 0 
> X(T&&) { cout << "tmpl " << endl; }

Which just involves adding a dummy default template argument (a known technique: Link). No extra headers are necessary.

You will get the desired output.

I got this answer from a related problem: Link. All this looks rather inelegant, but seems to be the only way out for now. I still would like to see a more elegant solution.

like image 43
alfC Avatar answered Oct 18 '22 11:10

alfC