When an argument is passed to a function template deducing the argument's type, it is an interesting question as to how the argument should be passed. It is worth noting that the exact nature of the use of the argument doesn't matter: Whether the argument is used as a function object, an iterator, a value, etc. is fairly irrelevant to answer. There are four options:
template <typename T> void f(T&& a)
template <typename T> void f(T& a)
template <typename T> void f(T const& a)
template <typename T> void f(T a)
It matters a bit as to what the function template actually does: If all f()
does with its argument a
is to forward it to another function, you want to pass by universal reference. The called function will sort out the details an reject inappropriate options. Of course, although generally useful, forwarding functions are somewhat boring.
If f()
actually does something with the object, we can normally immediately discard two of the options:
- Pass by universal reference results in a type of which we don't know if it is a reference or a value. This is pretty useless for anything other than forwarding the type as it either behaves like a value or like reference. Just specifying what the function actually does would be a problematic dance.
- Pass by
T const&
isn't doing us much good either: we got a reference to an object whose life-time we can't control and which we can neither move from nor get it copy/move-elided.
Passing an object by non-const
reference can be useful if the object itself gets modified. This is clearly an important part of the contract of the function and an imposed design choice which can't be overridden by a client of the function when taken.
The preferred approach is to take arguments by value. Generally, this makes the definition of the function's behavior much easier and actually keeps the choice whether the type should follow value or reference semantics open for the user! Just because something is passed by value doesn't mean that the objects of interest are also passed by value. Specifically for the case of function object, the standard C++ library even provides a generic adapter giving a value type reference semantics: std::ref()
.
Of course, interface design is subtle and there will be cases where each one of the options is warranted. However, as a general rule of thumb, I think it is very simple:
- Forwarding functions use universal references.
- Functions doing something with the arguments use values.
... and, of course, these rules only apply to arguments to function templates whose type is deduced.