3

I am doing some experiments to try to understand how forwarding works and I get to situation where I do not understand.

When I compile with clang 3.8 -O3

class Foo {
  Foo(const std::string& s) : str(s) {}
  std::string str;
};

and

class Foo {
  Foo(std::string&& s) : str(std::forward<std::string&>(s)) {}
  std::string str;
};

Constructing Foo with Foo foo("this is a test") in the first case is almost 2 times faster.

Why?

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
gsf
  • 6,612
  • 7
  • 35
  • 64
  • 1
    I'm guessing copy elision. – Jonathan Potter Jan 01 '16 at 21:23
  • Do you mean `std::forward(s)` (or simply `std::move(s)`) or `&` are here on purpose ? – Jarod42 Jan 01 '16 at 21:23
  • @Jarod42 yes, with '&' it does not compile without it. "error: no matching function for call to 'forward'" – gsf Jan 01 '16 at 21:25
  • @gsf: Not sure what you mean, the 3 variants compile (&, &&, nothing) [Demo](http://coliru.stacked-crooked.com/a/4ec2344c9e222fe3). – Jarod42 Jan 01 '16 at 21:31
  • 2
    Note that with `&`, you make a copy of the temporary – Jarod42 Jan 01 '16 at 21:36
  • @Jarod42 Oops, sorry, the compile error was triggered from another test that was passing std::string("this is a test"). Without & is way better, just about 5% slower, but as it become clear from the answers using forward in this case is wrong anyways. I just did it because nothing else did any difference. – gsf Jan 01 '16 at 21:38

2 Answers2

9

You need to perfect-forward using std::forward only when dealing with forwarding references. Forwarding references only exist in the context of template deduction.

void f(std::string&& x): x is a regular rvalue-reference, because no template type deduction is taking place.

template<typename T> void f(T&& x): x is a forwarding reference, because of T template deduction.

Generally, you don't want to use std::forward unless you're dealing with forwarding references.

When calling std::forward, you have to pass the exact type of the forwarded value. This can be done as such: std::forward<decltype(x)>(x).

Or as such, when you have a name for the deduced type:

template<typename T> 
void f(T&& x)
{
    something(std::forward<T>(x));
}

I would write your code like this:

class Foo {
  template<typename T> 
  Foo(T&& s) 
      : str(std::forward<decltype(s)>(s)) {}

  std::string str;
};
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • Why would you write instead of ? – Johan Lundberg Jan 01 '16 at 22:19
  • Mostly because I often find myself in situations where I do not have a `T` available - think of generic lambdas, for example. I therefore created a `#define FWD(x) ::std::forward(x)` macro that helps me avoid repetition/mistakes. I think `(x)` is much less error-prone than `(x)`, and the verbosity can be reduced with the aforementioned macro. Also, I cannot think of a context where `(x)` is unusable, but `(x)` cannot be used in generic lambdas, for example. – Vittorio Romeo Jan 02 '16 at 00:38
  • Interesting, and I agree. Now, isn't `decltype(s)` `T&&` ? Is it that reference collapsing and the way `std::forward` works makes `` and `` do the same thing? – Johan Lundberg Jan 02 '16 at 11:39
  • *"Never use `std::forward` unless you're dealing with forwarding references."*, why not? It's useful even in scenarios where template argument type deduction is not involved – Piotr Skotnicki Jan 02 '16 at 14:05
  • @PiotrSkotnicki: "never" is probably an exaggeration *(I'll edit the post)*, yeah, but I cannot recall any instance of using `std::forward` outside of template argument deduction contexts. Could you provide a practical example? – Vittorio Romeo Jan 02 '16 at 14:37
  • 1
    @VittorioRomeo [How std::function works](http://stackoverflow.com/q/14936539/3953764) – Piotr Skotnicki Jan 02 '16 at 14:38
  • Thanks, that's a good example - I've actually used `forward` many times like that in my code, now that I think about it. – Vittorio Romeo Jan 02 '16 at 14:40
2

I think this is supposed to be a move constructor, and as such, should use std::move:

class Foo {
  Foo(std::string&& s) : str(std::move(s)) {}

  std::string str;
};

std::forward only makes sense for what Scott Myers calls "universal references", which is a subset of rvalue references - Vittorio talks about that in his answer.

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335