Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Neat way to parametrize function template with generic function pointer

Consider following case: I have

int bar1();
double bar2();

I want:

foo<bar1>(); // calls bar1, then uses its result.
foo<bar2>(); // calls bar2, then uses its result.

Naive way to write template foo() is to use additional parameter:

template <typename T, T (*f)()> void foo () {
  // call f, do something with result
}

This works, but I need to do ugly syntax:

foo<decltype(bar1()), bar1>(); // calls bar1, then uses its result

I want to write something pretty, like above, just foo<bar1>.

P.S. Please do not recommend to accept argument at runtime. I need compile time parametrization with function pointer only.

P.S. Sorry forget to mention: I am looking for C++14 solution. C++17 appreciated and I upvoted answer with C++17 solution, but project now builds with C++14 and I can not change it in nearest future.

like image 508
Konstantin Vladimirov Avatar asked Aug 11 '17 13:08

Konstantin Vladimirov


3 Answers

In order to get

foo<bar1>();

You need template<auto> from C++17. That would look like

int bar1() { return 1; }
double bar2() { return 2.0; }

template<auto function> void foo() { std::cout << function() << "\n"; }

int main()
{
    foo<bar1>();
    foo<bar2>();
}

Which outputs

1
2

Live Example

Before C++17 you have to specify the type as there is no auto deduction of the type of a non type template parameters.

like image 159
NathanOliver Avatar answered Dec 31 '22 06:12

NathanOliver


So, I'll try to give the best possible answer that I'm aware of in 14. Basically a good approach (IMHO) to this problem is to "lift" the function pointer into a lambda. This allows you to write foo in the much more idiomatic way of accepting a callable:

template <class F>
void foo(F f);

You still get optimal performance, because the type of the lambda is unique, and so it gets inlined. You can more easily use foo with other things though. So now we have to turn our function pointer into a lambda that is hardcoded to call it. The best we can on that front is drawn from this question: Function to Lambda.

template <class T>
struct makeLambdaHelper;

template <class R, class ... Args>
struct makeLambdaHelper<R(*)(Args...)>
{
  template <void(*F)(Args...)>
  static auto make() {
    return [] (Args ... args) {
      return F(std::forward<Args>(args)...);
    };
  }
};

We use it like this:

auto lam = makeLambdaHelper<decltype(&f)>::make<f>();

To avoid having to mention it twice, we can use a macro:

#define FUNC_TO_LAMBDA(f) makeLambdaHelper<decltype(&f)>::make<f>()

You could then do:

foo(FUNC_TO_LAMBDA(bar1)); 

Live example: http://coliru.stacked-crooked.com/a/823c6b6432522b8b

like image 23
Nir Friedman Avatar answered Dec 31 '22 05:12

Nir Friedman


I am looking for C++14 solution. C++17 appreciated and I upvoted answer with C++17 solution, but project now builds with C++14 and I can not change it in nearest future.

Unfortunately what you ask works starting from C++17.

If you want use the exactly syntax

foo<bar1>();

I don't thinks it's possible in C++14.

But, if you accept a little different syntax... I know that macros are distilled evil but... if you accept to call foo() as

FOO(bar1)();

you can define the macro

#define FOO(f) foo<decltype(f()), f>

A full working example

#include <iostream>

#define FOO(f) foo<decltype(f()), f>

int bar1 ()
 { std::cout << "bar1()" << std::endl; return 0; }

double bar2 ()
 { std::cout << "bar2()" << std::endl; return 1.0; }

template <typename T, T (*f)()>
void foo ()
 { f(); }

int main()
 {
   FOO(bar1)(); // print bar1()
   FOO(bar2)(); // print bar2()
 }
like image 22
max66 Avatar answered Dec 31 '22 04:12

max66