0

So I have a std::map< std::string, boost::any > called OptionsMap and I want to create a function that takes any type (therefore the template) and stores it in the map. Would the next code work?

template <typename T>
void Set(std::string optionName, T&& optionValue)
{
    OptionsMap[optionName] = optionValue;
}
Shoe
  • 74,840
  • 36
  • 166
  • 272
ReBirTH
  • 91
  • 7

2 Answers2

3

Typically, when a function template accepts a universal reference (i.e. an rvalue reference of a deduced type), you should cast the function parameter with forward so as to obtain a value of the same value category as was provided by the argument in the function call:

template <typename T>
void Set(std::string optionName, T&& optionValue)
{
    OptionsMap[optionName] = std::forward<T>(optionValue);
    //                       ^^^^^^^^^^^^^^^
}

The parameter variable optionValue itself is always an lvalue, so without the cast, you would be making copies of what should be moves. If the type is copyable, this will be less efficient than expected; if the type isn't copyable (e.g. unique_ptr), it's worse: your function now accepts an argument that it will subsequently fail to compile with.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Thanks! The forward function returns a rvalue from a lvalue, if I understand correctly? – ReBirTH Jul 20 '14 at 20:52
  • 2
    @user3027108: No. That would be `std::move`. By contrast, `std::forward` must be given an explicit template argument, and it restores the correct generalized value category of the original argument. (E.g. see [this answer](http://stackoverflow.com/a/3582313/596781).) – Kerrek SB Jul 20 '14 at 20:54
  • The argument `optionName` shouldn't be a lvalue reference so that it doesn't create a `std::string` copy? – ReBirTH Jul 20 '14 at 21:10
  • @ReBirTH: Sure. I didn't want to argue with the OP about this unrelated detail. – Kerrek SB Jul 20 '14 at 21:12
1

Of course. Why wouldn’t it work?

boost::any::operator= accepts an argument of any data type satisfying ValueType.

I would just take by value and move it, for forward-compatibility when boost::any starts supporting move semantics.

template <typename T>
void Set(std::string optionName, T optionValue)
{
    OptionsMap[optionName] = std::move(optionValue);
}

Passing an rvalue to this function will move it when it’s movable. That’s guaranteed by the standard.

  • Yeah, but is it the most efficient/correct way of doing it? – ReBirTH Jul 20 '14 at 20:51
  • @user3027108 if it weren’t, why would I suggest it? –  Jul 20 '14 at 20:52
  • @rightfold OP mentions `T` as `T&&` (universal reference). You're writing it as a pass by value enforcing a copy to be made – Nikos Athanasiou Jul 20 '14 at 20:53
  • 1
    @NikosAthanasiou That’s not true. Passing an rvalue will move. By-value does not imply a copy. It only implies a copy when passing an lvalue, and `boost::any` makes a copy/does a move _anyway_. –  Jul 20 '14 at 20:54
  • @rightfold Passing an rvalue will create a temporary copy and then move that (unless you suppose a certain copy-ellision or optimization is guaranteed to work) – Nikos Athanasiou Jul 20 '14 at 20:55
  • 2
    @NikosAthanasiou Passing an rvalue to a function that takes by-value will always move when the type of the argument is move constructible. Copy elision is irrelevant. –  Jul 20 '14 at 20:56
  • @Jefffrey It's a faulty situation. If I have a resource out of the function that I want to move into it, then I'd have to pay for extra copy with the pass by value mode. Play by adding/removing the `&&` in this [toy example](http://coliru.stacked-crooked.com/a/21a9bd12a4f6b1f2) – Nikos Athanasiou Jul 20 '14 at 21:15