2

I have heard about “Perfect Forwarding” recently (from Scott Meyers Effective modern C++) like,

Case 1. Not using Perfect Forward

class Widget {
public:
    void setName(const std::string& newName) { name = newName; }
    void setName(std::string&& newName) { name = std::move(newName); }
...

Case 2. Using Perfect Forward

class Widget {
public:
    template<typename T>
    void setName(T&& newName) { name = std::forward<T>(newName); }
...

So now, I know there are lots of advantage of using perfect forwarding. but in case 1, the user knows the parameter type of setName is std::string. However, in case 2, it can't.

So my questions are:

  1. When using perfect forwarding, how user know what parameter type to pass?
  2. How to overcome this disadvantage?
Cinakyn
  • 648
  • 1
  • 7
  • 20
  • 2
    The type could be anything... the purpose of using a template is so that your code can handle many types. So you can't "know" as such what the parameter is. It's not a disadvantage. What is the larger problem you are trying to solve? – M.M Dec 23 '15 at 04:39
  • That “perfect forwarding” code is not from Meyer's book. Instead of `std::move`, you'd use `std::forward` and, well, that's the type. – 5gon12eder Dec 23 '15 at 04:39
  • originally `setName` method was accepting `std::string`, and type of member variable `name` is also `std::string`. we changed `setName` method's parameter type to template type for using 'perfect forwarding'. but now, if user put other type (ex. int, float, bool...) than `std::string`, compile error occur. if you didn't heard about perfect forward, please check this link http://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c/ @M.M – Cinakyn Dec 23 '15 at 04:43
  • @5gon12eder sorry. that was mistake. I changed it. – Cinakyn Dec 23 '15 at 04:44
  • 3
    If you call version 1 with `int`, `float`, `bool`, … you'd get a compiler error as well. Don't you? I think you mean: How does the *caller* know what type to pass? Can your question be re-phrased to that? – 5gon12eder Dec 23 '15 at 04:47
  • I already know about perfect forwarding.. thanks for the link though – M.M Dec 23 '15 at 04:48
  • As others have mentioned, if the user calls the function with an invalid type then the compiler will helpfully tell them what is wrong. Alternatively, you could document that `T` is expected to any type convertible to the type of `name` (I guess that's `std::string` in this case). – James Adkison Dec 23 '15 at 04:50
  • This case is easy to solve in that way because type of `name` is `std::string` but if `name`'s type is custom class and uses custom methods of library, caller will be really confused. @JamesAdkison – Cinakyn Dec 23 '15 at 04:59
  • @Cinakyn If it's not beneficial to support a conversion from type `T` then why not just stick with version 1? – James Adkison Dec 23 '15 at 05:02
  • To answer your questions: (1) user does not know, (2) Nobody's forcing you to use this pattern. Don't use it if you don't like it. – M.M Dec 23 '15 at 05:02
  • @M.M you are right... so I changed my question – Cinakyn Dec 23 '15 at 05:04
  • to answer the new questions: (1) the caller looks at the type of `name` and/or reads documentation, (2) don't worry about it, don't use it if you think it is more annoying than useful – M.M Dec 23 '15 at 05:08
  • 1
    @5gon12eder I'll point out that you *can* assign a `int`, `float`, or `bool` to `std::string` (which is arguably a drawback of the second approach in this case). It doesn't do what you might expect it to do, but you can do it, unfortunately. – T.C. Dec 23 '15 at 07:41
  • @T.C. Oh dear, I didn't even know that. I thought only the `+=` operator was overloaded to accept integers… – 5gon12eder Dec 23 '15 at 21:25

3 Answers3

3

You (the author of the code) will have to tell the user what types are acceptable. For the code shown in your example, a comment like

An object of type T shall be assignable to an lvalue of type std::string.

might be helpful. Otherwise they will learn it the hard way from their compiler's error messages, which indeed could be quite frustrating.

You could use SFINAE to restrict the set of acceptable Ts but I don't see what you would gain from doing this, except that it would probably prevent some desirable optimization if you are too restrictive (like forgetting to allow const char *). All it buys you is trading one compiler error (“cannot assign T to std::string”) for another (“no overload for call to Widget::setName(T)”). The picture will change when there are other overload candidates for that function but then again, see Scott Meyer's book about (rather not) overloading perfect forwarders.

As with any powerful feature, don't over-use perfect forwarding. If you feel that the gains don't outweigh the additional complexity in your use-case, feel free to refrain from using it. A simple “take by-value and apply std::move” is often a good and sufficient alternative.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
0

I think the solution here is to just pass the name by value:

class Widget {
public:
    void setName(std::string newName) { name = std::move(newName); }
...

So here if the name is rvalue, it will be moved by the way. if it's lvalue, it will be copied and since you are copying it in the const reference overload, no change.

Mehrdad
  • 1,186
  • 7
  • 15
  • While I agree that this is probably a good enough solution for the example in the question, note that it isn't as efficient as the perfect forwarder when called, say, with a string literal. – 5gon12eder Dec 23 '15 at 05:30
  • It isn't as efficient because in the case of lvalue, it would need a copy + a move. But in the case of string literal, you will finally construct a std::string, so there's not much difference if you construct it here and then move it, or move it and then construct it. – Mehrdad Dec 23 '15 at 06:25
  • @5gon12eder But more important is that most of the times, a better and clear api is much more desirable over an ambiguous slightly faster one. – Mehrdad Dec 23 '15 at 06:26
  • I agree about the simplicity and all. But note that if you call the perfect forwarder with a string literal (`const char[]`), you construct exactly zero `std::string`s. `name`'s assignment operator is called directly with a `const char *` argument and can copy the bytes out. The “take a `std::string` by-value and move-assign it” version constructs a temporary. – 5gon12eder Dec 23 '15 at 21:30
-2

The whole point of template is to make the code agnostic of the underlying type. That perfect forwarding works because it "forwards" the right type. (l-values stays l-values, r-values stays r-values)

The code you have shown in the question won't compile because std::forward needs a type to be called. If you just want to try out std::forward, with your restrictions the code would look something like:

template<typename T> 
class Widget {
public:
    void setName(T name) { static_assert(sizeof(T) == -1, "class must be instantiated with std::string"); }
};
template<>
class Widget<string>{
public:
    void setName(string pname) { name = std::forward<string>(pname); }
    string name;
};

Note that this is not what perfect forwarding means. For more info check this: Why does a perfect forwarding function have to be templated?:

Community
  • 1
  • 1
bashrc
  • 4,725
  • 1
  • 22
  • 49
  • Indeed, this is not what perfect forwarding means. I'm sorry, what's your point? – 5gon12eder Dec 23 '15 at 04:59
  • @5gon12eder Clarified here: "If you just want to try out std::forward, with your restrictions the code would look something like" That is what I could decipher from op's question. – bashrc Dec 23 '15 at 05:01
  • `std::forward` should be `std::move`. The whole idea of *forwarding* is to retain the value category of the arguments – M.M Dec 23 '15 at 05:04
  • But the OP's use of `std::forward` in the question's current form is just fine. – 5gon12eder Dec 23 '15 at 05:04
  • @5gon12eder In the original version of the question on which I replied OP had std::forward(paramName); That is obviously wrong.. – bashrc Dec 23 '15 at 05:08