24

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.

max66
  • 65,235
  • 10
  • 71
  • 111
Konstantin Vladimirov
  • 6,791
  • 1
  • 27
  • 36
  • Can you explain why it needs to be a template parameter? If you need it at compile time, how about making `foo` be `constexpr`? – Vaughn Cato Aug 11 '17 at 14:22
  • @VaughnCato, because calling a template non-type parameter is guaranteed to **not** be an indirect call. – SergeyA Aug 11 '17 at 14:27
  • Well, solving your problem in 14, without naming `bar1` twice, without using a macro, is impossible, it's as simple as that. If you want to see the best possible 14 solution that minimally leverages a macro I'm happy to post it. – Nir Friedman Aug 11 '17 at 14:27
  • Playing devil's advocate: I assume, macros are out of question? – SergeyA Aug 11 '17 at 14:27
  • @SergeyA It isn't clear why that guarantee is important. If performance is an issue, with optimization, it shouldn't be an indirect call in practice. – Vaughn Cato Aug 11 '17 at 14:43
  • @VaughnCato how do you know? If function is not inlined, call will be indirected. – SergeyA Aug 11 '17 at 14:47
  • @SergeyA I tried it: https://godbolt.org/g/6HqbAJ – Vaughn Cato Aug 11 '17 at 14:52
  • 1
    @VaughnCato If you pass `bar1` as a function pointer into `foo`, and `foo` calls `bar1`, that call will almost certainly not be inlined unless `foo` in its entirety is inlined. You may find this surprising as many C++ experts I've talked to expected otherwise. But I've done this experiment a dozen times with `std::sort`; compare passing a function pointer vs lambda into `sort` (which is too big to get inlined). – Nir Friedman Aug 11 '17 at 14:53
  • 1
    @VaughnCato Your example is incorrect because it assumes that `foo` gets inlined as well. https://godbolt.org/g/NTi3oF. – Nir Friedman Aug 11 '17 at 14:55
  • @NirFriedman Not sure what you mean by incorrect. It is inlined in my example, so it demonstrates that the call can be direct. If the OP has a case where it is not being inlined, that would be useful to show. – Vaughn Cato Aug 11 '17 at 14:57
  • My concern is that without context, this could easily be an XY problem. – Vaughn Cato Aug 11 '17 at 14:58
  • 1
    @VaughnCato Incorrect means that you said "it shouldn't be an indirect call in practice", and used that example to back it up. `foo` not getting inlined is part of practice, so your example does not back up what you said. There is absolutely no reason to think that this is an XY problem, please don't overuse that because it's incredibly frustrating for the question asker. – Nir Friedman Aug 11 '17 at 15:04
  • 1
    @VaughnCato, i think, OP demonstrated sufficient proficiency to assume it is not XY problem. Blaming everything to be XY problem is very counterproductive. – SergeyA Aug 11 '17 at 15:05
  • @NirFriedman Agreed. I overgeneralized. I should have said, I don't know a case where the call can't be inlined, assuming you have full control over the implementation of `foo`. – Vaughn Cato Aug 11 '17 at 15:21
  • You want to get the type but you don't want to write `decltype`. Perhaps you should revisit your requirements. – Henri Menke Aug 13 '17 at 05:51

5 Answers5

27

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
4

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

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • @skypjack No. I don't understand your argument. It's a specialization for function pointer types. `R(*)(Args...)` is the type of a function pointer taking `Args` and returning `R`. I think you are getting confused with the syntax used by e.g. `std::function`. I added a live example, so as you can see, it works as is... – Nir Friedman Aug 11 '17 at 19:10
  • 2
    Actually I didn't get the whole thing of your answer. It still smells a bit to me, but that's an answer and the OP will judge if it works for the real case. Thank you. – skypjack Aug 11 '17 at 20:16
  • `Args && ... args` is completely broken. Consider `int i = 1; void f(int); FUNC_TO_LAMBDA(f)(i);` – T.C. Aug 12 '17 at 08:32
  • Interesting example, thanks. But lambda helper now seems runtime function argument. I do not want to hope, that inliner will do something, because inlining has its limitations. – Konstantin Vladimirov Aug 12 '17 at 08:38
  • @T.C. Should it just be `Args ... args`? I guess the reference-ness will be deduced from the function signature so that will be correct. – Nir Friedman Aug 12 '17 at 13:40
  • @KonstantinVladimirov No, it's not a runtime argument in the equivalent situation. The *type* of the lambda is what allows inlining and that is obviously deduced at compile time. My example is completely trivial for the compiler to inline. You are welcome to try it. Note that the lambda is stateless, which of course is also known because its determined by the lambda's type. So the specific template instantiation looked at here, can get treated by the compiler as a function taking no arguments, so nothing is even passed at runtime! – Nir Friedman Aug 12 '17 at 13:41
2

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()
 }
max66
  • 65,235
  • 10
  • 71
  • 111
0

OK, so you asked specifically for bar1 and bar2 to be functions, but if you were willing to relax that constraint and instead allow them to be classes having a static member function that implements the desired behavior you could do it in the following way, which doesn't even require C++11 -

struct bar1 {
  static int f() { return 42; }
};

struct bar2 {
  static double f() { return 3.14159; }
};

template<typename bar>
void foo()
{
  double x = bar::f();
  std::cout << x << std::endl;
}

int main(int argc, char* const argv[])
{
  foo<bar1>();
  foo<bar2>();
}
gcbenison
  • 11,723
  • 4
  • 44
  • 82
-3

How about this?

template<typename F>
auto foo(F* f)
{
    return f();
}

int bar() { return 1; }

int main()
{
    return foo(bar);
}
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
  • From the question: "P.S. Please do not recommend to accept argument at runtime. I need compile time parametrization with function pointer only." – Nir Friedman Aug 11 '17 at 15:16