Pass pointer-to-template-function as function argument?
Asked Answered
M

7

14

Say I want a C++ function to perform arithmetic on two inputs, treating them as a given type:

pseudo:

function(var X,var Y,function OP)
{
 if(something)
  return OP<int>(X,Y);
 else if(something else)
  return OP<double>(X,Y);
 else
  return OP<string>(X,Y);
}

functions that fit OP might be like:

template <class T> add(var X,var Y)
{
 return (T)X + (T)Y; //X, Y are of a type with overloaded operators
}

So, the question is what would the signature for function look like? If the operator functions are non-templated I can do it, but I get confused with this extra complexity.

Millisecond answered 15/8, 2009 at 21:37 Comment(4)
Look at template template arguments. (And that's not a typo.)Downe
+1, that's basically the correct answer how to pass OP.Hine
I added this as an answer. I hope I didn't put any stupid errors into it.Downe
It's hard to create object of 'function type' (to get the function pointer), but easily to create a struct object...Runt
S
10

Template functions cannot be passed as template arguments. You have to manually deduce template arguments for this function before you pass it to another template function. For example, you have function

T sum(T a, T b)
{
    return a + b;
}

You want to pass it to callFunc:

template<typename F, typename T>
T callFunc(T a, T b, F f)
{
    return f(a, b);
}

You can't simply write

int a = callFunc(1, 2, sum);

You have to write

int a = callFunc(1, 2, sum<int>);

To be able to pass sum without writing int, you have to write a functor - struct or class with operator() that will call your template function. Then you can pass this functor as template argument. Here is an example.

template<class T>
T sum(T a, T b)
{
    return a + b;
}
template<class T>
struct Summator
{
    T operator()(T a, T b)
    {
        return sum<T>(a, b);
    }
};
template<template<typename> class TFunctor, class T>
T doSomething(T a, T b)
{
    return TFunctor<T>()(a, b);
    //Equivalent to this:
    //TFunctor<T> functor;
    //return functor(a, b);
}


int main()
{
    int n1 = 1;
    int n2 = 2;
    int n3 = doSomething<Summator>(n1, n2); //n3 == 3
    return 0;
}
Snowshoe answered 22/2, 2013 at 10:56 Comment(5)
This confusion wouldn’t arise in the first place if people used the correct terminology: “template functions” don’t exist, they are “function templates”. That is, in your example callFunc(1, 2, sum); you are not passing a function to callFunc, you are passing a template to it (and as your example shows you can pass templates as template arguments, but only class template, not function templates).Mannerless
Hm.. Didn't think about it this way. I believe the original question should be "How to pass function template as function template argument" then, right?Snowshoe
So, how would you make this solution work if sum is a non-static member of a class?Phosphoric
This is an absolutely fabulous approach. The only improvement I would make is to make the method static. For instance, call the method run. This will allow you to avoid having to create a new class instance each time you call the sum codeArborvitae
You can even move the template from the class to the operator () so you have a regular class Summator with a template <class T> T operator()(T a, T b). Then you can use a regular template for doSomething instead of a template template.Platon
L
5

Are you looking for this?

template<class T> T add(T X, T Y)
{
    return X + Y;
}

Or are you looking for something that calls something like add?

template<class T, class F>
T Apply(T x, T y, F f)
{
    return f( x, y );
}

Called via:

int x = Apply( 2, 4, add<int> );
Lazarolazaruk answered 15/8, 2009 at 21:39 Comment(1)
I believe he wants to pass add without specifying int as template argument.Snowshoe
M
5

I'm a bit confused … why the type differentiation in your pseudo-code?

C++ templates allow full type deduction on templates:

template <typename T, typename F>
T function(T x, T y, F op) {
    return op(x, y);
}

Here, F fits anything (especially functions) that may be called with the () function call syntax and accepting exactly two arguments of type T (or implicitly convertible to it).

Mannerless answered 15/8, 2009 at 21:40 Comment(3)
I think this is what I meant, didn't think of a function as a template argument.Millisecond
The only problem is that F cannot be template function with unknown template arguments, it either has to be a non-template function, or template function with all its template types specified.Snowshoe
@Snowshoe You can also explicitly specify template arguments. Deducing them can be done using template metaprogramming. But anyway it appears that this answered OP’s question, no need to complicate it further.Mannerless
G
4

I use lambdas for this.

auto add = [](const auto& lhs, const auto& rhs) {
    static_assert(std::is_arithmetic<typename std::decay<decltype(lhs)>::type>::value,
            "Needs to be arithmetic.");
    static_assert(std::is_arithmetic<typename std::decay<decltype(rhs)>::type>::value,
            "Needs to be arithmetic.");
    return lhs + rhs;
};

template<typename LHS, typename RHS, typename FUNC
    , typename OUT = typename std::result_of<FUNC(LHS, RHS)>::type>
constexpr OUT do_arithmetic(LHS lhs, RHS rhs, FUNC func) {
    return func(lhs, rhs);
}

constexpr auto t = do_arithmetic(40, 2, add);
static_assert(t == 42, "Wrong answer!");
static_assert(std::is_same<std::decay<decltype(t)>::type, int>::value,
        "Should be int.");
Godless answered 22/5, 2017 at 18:59 Comment(2)
You deserve more upvotes! Lambdas are the modern way of solving this issue, not functors.Zalea
start from C++14, lambda function (Generic lambda function) parameter's type can be declared as auto, it's a "generic lambda" (like templatized lambda)Reubenreuchlin
P
3
template <class OP> void function(OP op)
{
  // call with int
  op(1, 2);
  // or with double
  op(1.2, 2.3);
  // call with explicit template argument
  op.template operator()<int>(1, 2);
  op.template operator()<string>("one", "two");
}

struct Add
{
  template <class T> T operator ()(T a, T b)
  {
    return a + b;
  }
};

function(Add());
// or call with C++14 lambda
function([](auto a, auto b) { return a + b; });
Platon answered 22/3, 2021 at 19:6 Comment(2)
This answer is my favorite, simple and enough!Reubenreuchlin
this answer is very enlightening, when you want to treat a function more dynamically, for example, alias it's name, passing it to other functions as parameter, you need to turn it into a functor: #357450Reubenreuchlin
I
1

I think you're looking for the Strategy Pattern.

Indict answered 15/8, 2009 at 22:6 Comment(0)
D
0

I'm not sure what this var thing in your question means. It's certainly not a valid C++ keyword, so I assume it's a type akin to boost:any. Also, the function is missing a result type. I added another var, whatever that might be. The your solution could look like this:

template< template<typename> class Func >
var function(var X, var Y, Func OP)
{
 if(something)
  return OP<int>(X,Y);
 else if(something else)
  return OP<double>(X,Y);
 else
  return OP<string>(X,Y);
}

The funny template argument is a template itself, hence its name "template template argument". You pass in the name of a template, not an instance. That is, you pass std::plus, not std::plus<int>:

return function( a, b, std::plus );
Downe answered 17/8, 2009 at 18:53 Comment(3)
Doesn't work in Visual C++ 2008, Visual C++ 2010 due to compilation error.Snowshoe
@izogfif: Now imaging, for a moment, you had provided the exact compiler error. Someone might have come along, looked at it, understood what the problem is, and posted a solution. Of course, we don't want this, so it's good you haven't provided that.Downe
Good point. Here is the code I tried to compile and errors raised by compiler (at the end of the code): pastebin.com/YyhX9ruTSnowshoe

© 2022 - 2024 — McMap. All rights reserved.