Suppose I want to write a function that calls a nullary function 100 times. Which of these implementations is best and why?
Or is there a better implementation?
I would use the first one (pass the callable by value).
If a caller is concerned about the cost of copying the callable, then they can use std::ref(f) or std::cref(f) to pass it using reference_wrapper.
By doing this, you provide the most flexibility to the caller.
But in the main code of the question, it JUST calls the f 100 times. No?
@Artyer Although those predate forwarding references.
The third one cannot be used for mutable lambdas so is a slight modification of the second one.
The fourth one cannot be called with const objects unless you copy them which becomes quite awkward with lambdas. You also cannot use it with pre-existing mutable lambda object without copying it or moving from it (losing it in the process) which is similar limitation to 1.
And there you have it. There is no silver bullet here and there are different pros & cons to each of these versions. I tend to lean towards the first one being the default but with certain types of capturing lambdas or bigger callables, it might become an issue. And you cannot call the 1) with the mutable object and get an expected result. As mentioned in the other answer some of these can be overcome with std::ref and other ways of manipulating the actual T type. In my experience, these tend to be the source of pretty nasty bugs though when T is then something different than one expects to achieve i.e. mutability of a copy or such.
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard the const at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't call operator()&&.
is that it can have more versions (copies of code) if you pass f in multiple ways. With MSVC or the gold linker with ICF, those copies only cost compile time unless they differ, and if they differ you probably want to keep them.
this one has the advantage of being value semantics; and following the rule of taking values unless you have good reason not to is reasonable. std::ref/std::cref let you call it with a persistant reference, and for prvalues c++17 guaranteed elision will prevent a spurious copy.
but that relies on people having && overloads on their operator(), which nobody does.
[SOLVED] Why do we need virtual functions in C++?