3

I'm trying write a function that is templated on three things:

  1. First type.
  2. Second type.
  3. Function with arguments First and Second type.

The code looks like this:

#include <iostream>
#include <typeinfo>
using namespace std;

// Assume that this function is in a library.  Can't be modified.
void bar(int x, int y) {
  cout << x << endl;
  cout << y << endl;
}

// My code is below:
template <typename Type1, typename Type2, void (*fn)(Type1, Type2)>
void foo(Type1 x1, Type2 x2) {
  fn(x1,x2);
}

int main() {
  foo<int, int, &bar>(1,2);
}

The code works but I'm unhappy that my template has to include <int, int, &bar>. I hoped that the compiler would figure out that bar has int, int as parameters and figure it out.

I tried listing the function first and the types second but in the declaration, Type1 wasn't recognized in the function prototype because it is defined later in the same prototype.

Is there an elegant solution?

Edit: I definitely don't want to pass a pointer to bar on the stack. I want to be templated on bar. The params should be just (1, 2).

Edit2: And by that, I mean that I want to write foo<&bar>(1,2).

Eyal
  • 5,728
  • 7
  • 43
  • 70
  • 1
    Please clarify how would you like to use your `foo` function if you don't like `foo(1,2);` – Kamil Szot Jan 29 '15 at 12:00
  • It looks like functors with an overload of the `operator ()` is the approach used by most people. http://stackoverflow.com/questions/1174169/function-passed-as-template-argument – Richard Chambers Jan 29 '15 at 12:09
  • That is not a duplicate as the OP doesn't want to pass the function as a parameter on the stack, but as a non-type template parameter. – bolov Jan 29 '15 at 14:16
  • Can you explain why do you insist on not passing bar as a parameter? If performance is an issue, passing (syntactically) as a parameter does not necessary mean it will be really passed (on a stack or in a register). – Suma Jan 30 '15 at 10:38
  • @Suma, my example is simplified. If bar were defined in a different file and linked it, it would be on the stack, right? Using templates ensures that it isn't on the stack, I just wanted a shorter template definition. – Eyal Feb 03 '15 at 09:04
  • @Eyal Not necessarily. It should be easy for you to check the disassembly of the optimized executable and to check it yourself for both my and bolov's approach. I do not see anything preventing inlining here, the `bar` itself does not need to be inlined, it is enough to inline the template constructs. Note: for bolov's solution you may need to add `inline` keyword before functions depending on your compiler settings. – Suma Feb 03 '15 at 09:37

2 Answers2

3

This is my solution for not passing the function as a parameter:

void bar(int a, int b) {
  cout <<  a << " " << b << endl;
}

template <class F, F *fn>
struct Foo {

  template <class... Args>
  static decltype(auto) foo(Args &&... args) {
    return fn(std::forward<Args>(args)...);
  }
};

int main() {

  Foo<decltype(bar), bar>::foo(1, 2);

  return 0;
}

As you can see you have to write bar twice, once for it's type, once for it's value, but I think it is a small inconvenience.

Or the simple version (if you can't use c++11)

template <class F, F* fn>
struct Foo {

  template <class T1, class T2>
  static void foo(T1 t1, T2 t2) {
    fn(t1, t2);
  }
};

For those who don't mind passing the function obj as a parameter:

Option 1:

template <class T1, class T2>
void foo(T1 x1, T2 x2, void (*fn)(T1, T2)) {
  fn(x1, x2);
}

foo(1, 2, bar);

Option 2:

template <class T1, class T2, class F = void(*)(T1, T2)>
void foo(T1 x1, T2 x2, F fn)) {
  fn(x1, x2);
}

foo(1, 2, bar);

Option 3:

template <class T1, class T2, class F>
void foo(T1 x1, T2 x2, F fn)) {
  fn(x1, x2);
}

foo(1, 2, bar);

Option 3b (the real deal):

template <class T1, class T2, class F>
void foo(T1 &&x1, T2 &&x2, F &&fn)) {
  std::forward<F>(fn)(std::forward(x1), std::forward(x2));
}

foo(1, 2, bar);

Option 4 (the real real deal) (well.. depends on what you need)

template <class F, class... Args>
decltype(auto) foo(F &&fn, Args &&... args) {
  return std::forward<F>(fn)(std::forward<Args>(args)...);
}
bolov
  • 72,283
  • 15
  • 145
  • 224
  • I added a comment to my question to make it clear: I don't want to pass bar on the stack! I want to be templated on bar. Only the 2 ints are passed as parameters. – Eyal Jan 29 '15 at 12:21
  • Unless there are good reasons for restricting the generality, option 4 is the only proper choice. – Deduplicator Jan 29 '15 at 12:31
  • I made an edit to remove the trailing `;` that was on the end of the macro definition. A macro definition should always be one of these forms: `foo` or `foo(..)` or `(...)` or `do { ... } while(0)`, but nothing else – Aaron McDaid Jan 29 '15 at 13:21
  • (@bolov, You meant 'macro', not 'template', I guess). – Aaron McDaid Jan 29 '15 at 13:29
  • Ok silly me, easy fix to remove that verbose `std::add_pointer_t` non-sense. You still have to write bar twice: once for it's type and once for the symbol. So for me it looks pretty ok, no need for macros. – bolov Jan 29 '15 at 13:32
0

You can even avoid having to type bar twice using a temporary struct containing the value. The structure will be optimized out by any decent compiler, and the bar will not be passed on a stack, in spite of being passed as an argument to the foo:

template <typename Fn>
struct FooT {
  Fn &fn;
  FooT(Fn fn):fn(fn){}
  operator Fn *() {return fn;}
};

template <typename Fn>
inline FooT<Fn> foo(Fn *fn) {return FooT<Fn>(fn);}


int main() {
  foo(bar)(1,2);
}

Note: conversion to Fn * allows fn to be called directly, without any need for argument forwarding. It is also possible to call it using a template operator () passing/forwarding arguments to it if desired, but this seems simpler and works. The templated implementation might be handy in case you need to perform something more before or after calling the fn. I am not forward arguments, just passing them, to keep it simple, this can be changed easily if necessary:

template <typename Fn>
struct FooT {
  Fn &fn;
  FooT(Fn fn):fn(fn){}
  template <typename Type1, typename Type2>
  void operator ()(Type1 x1, Type2 x2) {
    fn(x1,x2);
  }
};

Visual Studio 2012 generated code, Whole Program Optimization disabled, expand only inline (/Ob1):

  foo(bar)(1,2);
011E16F0  push        2  
011E16F2  push        1  
011E16F4  call        bar (011E13D0h)  
011E16F9  add         esp,8  
Suma
  • 33,181
  • 16
  • 123
  • 191
  • Can you guarantee that optimization if bar is defined in a different file and linked in? – Eyal Feb 03 '15 at 09:06
  • @Eyal I cannot guarantee anything, I even do not know what compiler are you using. However, as `bar` is not involved in the optimization, it should not matter - note that even in my example it is not inlined. Only the `foo` function itself needs to be inlined, and this is possible even for `bar` from a different file. – Suma Feb 03 '15 at 09:34