mg84
mg84

Reputation: 287

Class with templated constructor as well as copy and move constructor

This question is a follow up to this one: Explicit template specialization for templated constructor of templated class The answers given in the other question are of course right but it turned out that I was not quite asking what I wanted to ask - so here is a new question:

Consider the following code:

template<typename First, typename ... Rest> class var {
    public:

    var() {
        std::cout << "default" << std::endl;
    }

    var(const var& v) {
        std::cout << "copy" << std::endl;
    }

    var(var&& v) {
        std::cout << "move" << std::endl;
    }

    template<typename T>
    var(const T& t) {
        std::cout << "general lvalue" << std::endl;
    }


    template<typename T>
    var(T&& t) {
        std::cout << "general rvalue" << std::endl;
    }

};


int main()
{
    var<int> i0; // expect 'default' -> get 'default'

    var<int> i1(i0); // expect 'copy' -> get 'general rvalue'
    var<int> i2(std::move(i0)); // expect 'move' -> get 'move'

    std::string s("Hello");
    var<int> i3(s); // expect 'general lvalue' -> get 'general rvalue'
    var<int> i4(std::move(s)); // expect 'general rvalue' -> get 'general rvalue'
}

I wrote in the main function which constructors I expect and want to be called and which ones are actually called. Here are my questions:

1) Can you explain why the program does not behave as I expected?

2) How can I make the program to call the copy and move constructor of var when it gets a var and the templated constructors otherwise?

3) And finally, I'm trying to put the two templated constructors into one handling both lvalues and rvalues and forwarding them to another function using std::forward - how could this look like?

Upvotes: 3

Views: 4704

Answers (1)

Jonathan Wakely
Jonathan Wakely

Reputation: 171373

1) Can you explain why the program does not behave as I expected?

On this line:

var<int> i1(i0); // expect 'copy' -> get 'general rvalue'

The var(T&&) constructor is instantiated with T substituted with var<int>&, i.e. producing a constructor with this signature:

var(var&);

That constructor is a better match than the implicit copy constructor var(const var&) because i0 is non-const.

Similarly for:

var<int> i3(s); // expect 'general lvalue' -> get 'general rvalue'

s is non-const, so the var(T&&) constructor is instantiated with T substituted with std::string&, producing a constructor with the signature:

var(std::string&);

For a non-const argument that constructor is a better match than the other constructor template, which produces:

var(const std::string&);

You need to realise that the var(T&&) constructor is not a "general rvalue" constructor, because T&& can match any type including lvalues.

See Universal References in C++11 for more details.

2) How can I make the program to call the copy and move constructor of var when it gets a var and the templated constructors otherwise?

Constrain the templates so they don't accept any type.

template<typename T>
  using Is_a_var = std::is_same<typename std::decay<T>::type, var>;

template<typename T>
  using Enable_if_not_a_var = typename std::enable_if<!Is_a_var<T>::value>::type;

template<typename T, typename Constraint = Enable_if_not_a_var<T>>
var(T&& t) {
    std::cout << "general value" << std::endl;
}

I would also add defaulted copy/move constructors, to be clear to readers you want them:

var(const var&) = default;
var(var&&) = default;

3) And finally, I'm trying to put the two templated constructors into one handling both lvalues and rvalues and forwarding them to another function using std::forward - how could this look like?

Don't. The var(T&&) constructor already accepts both rvalues and lvalues.

Use std:forward<T>(t) to forward the argument to other functions:

template<typename T, typename Constraint = Enable_if_not_a_var<T>>
var(T&& t) : m_something(std::forward<T>(t)) {
    std::cout << "general value" << std::endl;
}

Upvotes: 7

Related Questions