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.

Mr. Boy
  • 60,845
  • 93
  • 320
  • 589

7 Answers7

9

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;
}
izogfif
  • 6,000
  • 2
  • 35
  • 25
  • 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). – Konrad Rudolph Feb 22 '13 at 13:16
  • 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? – izogfif Feb 22 '13 at 20:13
  • So, how would you make this solution work if sum is a non-static member of a class? – Scott Hutchinson Jul 03 '17 at 21:10
  • 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` code – bremen_matt May 09 '19 at 12:40
  • You can even move the template from the class to the `operator ()` so you have a regular class `Summator` with a `template T operator()(T a, T b)`. Then you can use a regular template for `doSomething` instead of a template template. – eyelash Mar 22 '21 at 18:43
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> );
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
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).

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • I think this is what I meant, didn't think of a function as a template argument. – Mr. Boy Aug 17 '09 at 19:36
  • 1
    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. – izogfif Feb 22 '13 at 10:59
  • @izogfif 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. – Konrad Rudolph Feb 22 '13 at 13:07
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.");
scx
  • 3,221
  • 1
  • 19
  • 37
  • You deserve more upvotes! Lambdas are the modern way of solving this issue, not functors. – Chris_128 Oct 06 '20 at 14:27
  • 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) – TingQian LI Jun 09 '21 at 03:31
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; });
eyelash
  • 3,197
  • 25
  • 33
  • This answer is my favorite, simple and enough! – TingQian LI Jun 09 '21 at 06:12
  • 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: https://stackoverflow.com/questions/356950/what-are-c-functors-and-their-uses – TingQian LI Jun 10 '21 at 01:33
1

I think you're looking for the Strategy Pattern.

rlbond
  • 65,341
  • 56
  • 178
  • 228
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 );
sbi
  • 219,715
  • 46
  • 258
  • 445
  • Doesn't work in Visual C++ 2008, Visual C++ 2010 due to compilation error. – izogfif Feb 22 '13 at 10:47
  • @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. – sbi Feb 22 '13 at 15:20
  • Good point. Here is the code I tried to compile and errors raised by compiler (at the end of the code): http://pastebin.com/YyhX9ruT – izogfif Feb 22 '13 at 20:29