I have a templated function that receives function-objects. Sometimes the function-objects are stateless structs, but sometimes they are large statefull objects. The state of the function-object is not changed in this function, only examined. I'm also very keen on writing code that the compiler can optimize as much as possible. What should I consider when choosing the argument type?
The function is of this type:
template<typename funcT>
auto make_particle(funcT fun) {
Particle<typename funcT::result_type> particle;
particle = fun();
return particle;
}
The argument type should probably be funcT const & fun
so that the large objects are not copied, but why do most people use call-by-value function objects? Do I loose something by using const reference? Or should I use lvalue-reference? Please note that c++1y is ok and that the code example above is just an example.
There are several use cases, which should all be available:
The functor has no state and is supplied as a temporary: make_particle(MyFun())
The functor has a state which needs to be recovered later: YourFun f; make_particle(f);
You cannot solve both cases with one single reference type parameter: The first case requires a const lvalue reference or an rvalue reference, which forbids the second use, and the second case requlres an lvalue reference, which forbids the first use.
A common idiom in such situations is to accept the functor by value, and return it at the end:
template <typename Iter, typename F>
F map(Iter first, Iter last, F f)
{
// ... f(*first) ...
return f;
}
That may not be entirely applicable in your case, though, but it's an idea. For example, you could return a std::pair<ParticleType, F>
. In any case you'd require your functor type to be copyable, but that's a reasonable requirement.
An alternative, helpfully pointed out by @Xeo, and available for function templates only, is to take the functor argument by universal reference, which will work in both cases:
template <typename Iter, typename F>
void map(Iter first, Iter last, F && f)
{
// ... use f(*first) ...
}
Note that in this case we do not use std::forward
, since we are using f
as a genuine reference, and not just to pass it through somewhere else. In particular, we are not allowed to move-from f
if we still plan to use it.
The argument type should probably be funcT const & fun so that the large objects are not copied,
That is not the view taken by the algorithms in the standard libraries. There, callable objects are taken by value. It's up to the author of the callable object to ensure that it's reasonably cheap to copy. For example if it needs access to something large, then you can have the user of the functor provide a reference to one and store that in the functor -- copying a reference is cheap.
Now, it may be that you want to do things differently from the standard library because you expect particle-making functions to be unusually difficult to make cheap to copy or move. But C++ programmers are familiar with functors being copied, so if you do what the standard library does then usually you aren't making their lives any worse than they were already. Copying functors isn't an issue unless you make it one :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With