8

I have the situation where one function calls one of several possible functions. This seems like a good place to pass a function as a parameter. In this Quoara answer by Zubkov there are three ways to do this.

int g(int x(int)) { return x(1); }
int g(int (*x)(int)) { return x(1); }
int g(int (&x)(int)) { return x(1); }
...
int f(int n) { return n*2; }
g(f); // all three g's above work the same

When should which method be used? What are there differences? I prefer the simplest approach so why shouldn't the first way always be used?

For my situation, the function is only called once and I'd like to keep it simple. I have it working with pass by pointer and I just call it with g(myFunc) where myFunc is the function that gets called last.

northerner
  • 756
  • 5
  • 21
  • 3
    None of them. Use a template parameter. – L. F. Oct 04 '19 at 23:47
  • 2
    The first two are completely equivalent. The third is almost the same as the first two, except it requires an lvalue. `g(+f);` works for the first two, but not the third. – Raymond Chen Oct 05 '19 at 00:52
  • @RaymondChen "The first two are completely equivalent" then in my view the first is obviously the correct choice since it's simpler. Why complicate it with a pointer? – northerner Oct 05 '19 at 01:17
  • 1
    On the other hand, in `int g(int x(int))`, `x` is a pointer even though it doesn't look like one. The corresponding global declaration `int x(int);` declares a function, not a function pointer. – Raymond Chen Oct 05 '19 at 02:17
  • [A godbolt link to back up @RaymondChen's claim](https://godbolt.org/z/rZbmAS). Note that the emitted assembly labels `x` as a pointer too. – Eric Oct 05 '19 at 12:12
  • Indeed, the original Quora answer said the same thing: "if you write a function taking another function as a parameter, it will be silently adjusted to a function taking a pointer." The original Quora answer also noted that C++ prefers callables over function pointers and gives the same examples as presented in the answers below. – Raymond Chen Oct 05 '19 at 14:48
  • @RaymondChen: `g(+f)` fails in the third case not because `+f` is not an lvalue, but because `+f` changes `f` from a reference to a pointer (!). `g(*+f)` works just fine. – Eric Oct 05 '19 at 17:19
  • @Eric Oops. Was trying to make a function rvalue, but now realized that those things don't exist. – Raymond Chen Oct 05 '19 at 17:56
  • I was hoping that `std::move(f)` could produce such an rvalue, but it behaves very strangely - asked [this question](https://stackoverflow.com/q/58252538/102441) to follow up on that. – Eric Oct 05 '19 at 21:40

1 Answers1

3

Expanding on L.F.'s comment, it's often better to eschew function pointers entirely, and work in terms of invocable objects (things which define operator()). All of the following allow you to do that:

#include <type_traits>

// (1) unrestricted template parameter, like <algorithm> uses
template<typename Func>
int g(Func x) { return x(1); }

// (2) restricted template parameter to produce possibly better errors
template<
    typename Func,
    typename=std::enable_if_t<std::is_invocable_r_v<int, Func, int>>
>
int g(Func x) { return std::invoke(x, 1); }

// (3) template-less, trading a reduction in code size for runtime overhead and heap use
int g(std::function<int(int)> x) { return x(1); }

Importantly, all of these can be used on lambda functions with captures, unlike any of your options:

int y = 2;
int ret = g([y](int v) {
    return y + v;
});
Eric
  • 95,302
  • 53
  • 242
  • 374
  • How are these called? Also how are these better? – northerner Oct 05 '19 at 21:33
  • They are called in exactly the same way as your signatures above. They're better because they work with lambdas, and other stateful functions. Note that all of `` uses this approach to accept callback functions. – Eric Oct 05 '19 at 21:41
  • Ok. To confirm, you don't need to call the templated function with a template identifier? For example you don't need `g(myFunc)` just `g(myFunc)`? – northerner Oct 05 '19 at 22:08
  • Correct, the idea is to let the tyoe parameter be inferred – Eric Oct 06 '19 at 08:59
  • Would templates still work if you're passing more than one function as a parameter? – northerner Oct 06 '19 at 10:08
  • Yes, you would just use a template parameter for each function. Take a look at the `BinaryOp2` overload of [`transform_reduce`](https://en.cppreference.com/w/cpp/algorithm/transform_reduce) for an example in the standard library. – Eric Oct 06 '19 at 10:22