10

I want to develop a small polymorphic class with type erasure and I wonder which version of the templatized constructor is better and should be used.

We can pass by value:

class A
{
    ...
    template< typename T >
    A( T t ) { /* create the underlying model via std::move */ }
    ...
};

or we can use a universal reference:

class A
{
    ...
    template< typename T >
    A( T &&t ) { /* create the underlying model via std::forward */ }
    ...
};

(The universal reference has to be enabled if for the case that T is not the class itself and the class is not copied). Any ideas? Both version look equal to me.

JFMR
  • 23,265
  • 4
  • 52
  • 76
headmyshoulder
  • 6,240
  • 2
  • 20
  • 27
  • possible duplicate of [How to pass parameters correctly?](http://stackoverflow.com/questions/15600499/how-to-pass-parameters-correctly) or http://stackoverflow.com/questions/14185985/passing-by-value-vs-const-and-overloads – stijn Jun 26 '13 at 09:35
  • It certainly depends on what happens to `t` inside. But I can't actually think of any reason why you'd got with the first option. It would imply that make a local copy of `t` in the constructor -- even if you end up making a permanent copy (in a data member), why would you want a local copy? – jogojapan Jun 26 '13 at 09:38
  • 2
    No, in the first version you can move t. – headmyshoulder Jun 26 '13 at 09:43
  • 2
    @jogojapan read the links I posted: it actually makes sense to use pass-by-value if you're going to make a copy anyway – stijn Jun 26 '13 at 09:43
  • @stijn Not sure what exactly you refer to. But note that I am talking about function-local copies vs. copies in data members. Making a function-local copy just for the purpose of then making another copy (into the data member) doesn't make such sense. At least not at first glance. – jogojapan Jun 26 '13 at 09:49
  • @jogojapan I stand corrected, it wasn't clear to me you (and the OP as well maybe?) are talking about taking a function-local copy - that indeed doesn't make too much sense – stijn Jun 26 '13 at 10:02
  • But you must not make a copy. You can move the objects from the outside into the function (and you can also make a copy). This is very similar to the universal reference. Inside the function, which in this case is a constructor, you move the data into the members. – headmyshoulder Jun 26 '13 at 11:57

1 Answers1

8

These are not equivalent and sometimes one is desired over the the other one. A stellar talk by Nicolai Josuttis is an hour worth of talking just about this. I highly recommend watching it at least once.

Personally, unless you encounter a special case, where conversions are expensive and you want to avoid temporaries as much as possible, I would suggest just passing by value and std::moveing the argument.

Case where T&& is more efficient:

struct foo {
    std::string bar;

    template <typename T>
    foo(T&& t)
        :bar(std::forward<T>(t)){}
};

versus:

struct foo {
    std::string bar;

    foo(std::string t)
        :bar(std::move(t)){}
};

when you do:

int main() {
    foo f("some char*");
}

In the first case (perfect forwarding) you simply construct a std::string with const char* argument. In the second case, you construct one temporary (t from "some char*") and one empty std::string object, then you apply one move operation. It's not the end of the world, but the first version is simply more efficient.

To be absolutely clear about performance:

  • The first version uses 1 allocation

  • the second version uses 1 allocation and 1 move

and by move I don't mean std::move, since it generates no code (it's just a cast). By move I mean the code that needs to be execuded that actually moves from the string, which is a part of std::string(std::string&&).

Once again - the above example was based on the talk I linked at the beginning of the answer. It's really worth watching.

Fureeish
  • 12,533
  • 4
  • 32
  • 62