Pass a templated function as argument to another function

Viewed 614

Problem

I am trying to make a function which searches for a type in a parameter pack, then creates a lambda which invokes another function with that type as a template parameter e.g.:

auto fn = findType<MyType, SomeType, OtherType>("OtherType");
fn(otherFn) == otherFn<OtherType>();

I would like to write something like this:

template<class T, class ...Ts>
auto findType(const std::string& name) {
    if (refl::is_reflectable<T>() && refl::reflect<T>().name == name) {
        return []<class Fn>(Fn fn) {
            fn<T>();
        };
    }
    return findType<Ts...>(name);
}

However, C++ doesn't seem to recognise that fn could be parameterised with template types.

I am using gcc10 and C++20, so if possible, I can also use concepts.

I believe the problem can be summed up as: How can I pass a template-parameterised function into another function?

template<class C>
void fn() {}

template<class Fn, class Arg>
void mainFn(Fn fn) {
    fn<Arg>(); // ???
}

Attempted searches

I had looked at template template parameters, but that seems to be only for templating types, not function calls.

I had also looked at C++ concepts, but std::invokable doesn't take in template parameters and requirements also don't seem to allow for such expressions:

return []<class Fn>(Fn fn) requires requires { fn<T>(); } {
4 Answers

Function parameters are variables. Not variable templates; just regular old variables. And a non-template variable cannot be given template parameters.

You cannot pass a function template anywhere. You can only pass a particular instantiation of a function template. The closest you can get to what you want is to pass a type that has a templated operator() overload, but unless you can provide the template parameters through deduction, the only way to invoke it is via fn.operator()<TemplateArguments>(params). So you may as well have given it a meaningful name.

The closest I can comeup with is to wrap the function template within class wrapper to achieve similar effect (Live):

#include <iostream>
using namespace std;

struct Fn
{
    template <class Arg>
    static void fn() {cout << "Fn::fn()\n";}
};

struct Gn
{
    template <class Arg>
    static void fn() { cout << "Gn::fn()\n"; }
};

template<class Arg, class F>
void mainFn(F dummy) {
    F::template fn<Arg>(); 
}

int main()
{
    mainFn<int>(Fn{});
    mainFn<int>(Gn{});
}

outputs

Fn::fn()
Gn::fn()

I just wanted to put the closest I have gotten to what I wanted for others who may have special cases where this is possible.

As Nicol Bolas has already said, it is only possible through type deduction. So, if you have control over the function you receive, you can actually use that:

template<class C>
void fn(C* ignore) {}

template<class Fn, class Arg>
void mainFn(Fn fn) {
    Arg* ignore = nullptr;
    fn(ignore);
}

This removes the type parameter and makes use of type deduction to find the type.

In my case, this is insufficient as RedFog has mentioned that my findType function actually returns different types on the seemingly same call.

For C++, a practical way to go about passing a function to another function would be as follows:

#include <iostream>
#include <functional>

// To build and run the code
// g++ -std=c++20 -O3 -Wall -Werror -Wshadow -pedantic file.cpp -o file && ./file

double add5(double input_value) {
    return input_value + 5;
}

template<typename T>
T add2(T input_value) {
    return input_value + (T)2;
}

// std::function<double(double)> func
// return type ----^      ^---------------\
// list of `func` input argument types ---/
double func(double input_value, std::function<double(double)> func) {
    return func(input_value);
}

int main() {
    double value1 = 10.75;
    double value2 = add2(value1);

    std::cout << "Value1: " << value1 << std::endl;
    std::cout << "Value2: " << value2 << std::endl;

    // Add 5 to value2 by passing value2 and function 'add5' to function 'func'
    double value3 = func(value2, &add5);
    std::cout << "Value3: " << value3 << std::endl;
}

If you compile and run this code, the output should look like

Value1: 10.75
Value2: 12.75
Value3: 17.75

However, if you try to pass the template function (add2) to func, you will receive something along the lines of error: no matching function for call to 'func'. C++ does not allow you to pass template functions to other functions, BUT C++ does allow you to pass specific instances of template functions to other functions. If you replace

double value3 = func(value2, &add5);

with

double value3 = func(value2, &add2<double>);

at the bottom of main(), this will achieve the desired result.

Value1: 10.75
Value2: 12.75
Value3: 14.75

Unfortunately C++ does not allow you to pass a generalized template function to other functions at the moment.

Related