Home > Back-end >  Why does std::function not work with function templates?
Why does std::function not work with function templates?

Time:09-17

I am starting to work with templates and I am trying to figure out why the following does not work.

class testclass1
{
public:
    
    template <typename...Ts>
    using TFunc = std::function<void(Ts*...)>;
    
    template <typename...Ts>
    void SetFunction(TFunc<Ts...> tf)
    {
        // Do something
    }
};

template<typename...Ts>
class testclass2
{
public:
    
    using TFunc = std::function<void(Ts*...)>;
    
    void SetFunction(TFunc tf)
    {
        // Do something
    }
};

void some_function(std::string*, double*)
{
    
}

int main()
{
    
    testclass1 tc1;
    testclass2<std::string, double> tc2;
    
    testclass1::TFunc<std::string, double> tf1 = some_function;

    tc1.SetFunction<std::string, double>(tf1);
    tc1.SetFunction<std::string, double>(testclass1::TFunc<std::string, double>{tf1});
    
    tc2.SetFunction(some_function);
    
    tc1.SetFunction<std::string, double>(some_function);
}

All works fine except the last version tc1.SetFunction<std::string, double>(some_function); which is actually what I would like to do since it has much less boilerplate and since I don't want testclass1 to require template arguments.

The last line gives the following compilation error:

No matching member function for call to 'SetFunction'

  1. Candidate template ignored: could not match 'function<void (std::__1::basic_string *, double *, type-parameter-0-0 ...)>' against 'void ()(std::__1::basic_string *, double *)'

I don't understand what is the additional "type-parameter-0-0". Intuitively, this looks much like problems linked to an implicit *this pointer, but I don't see why TFunc would be a member function and have an implicit *this pointer (or is this linked to std::function?). Also, moving the code template <typename...Ts> using TFunc = std::function<void(Ts*...)>; outside of testclass1 does not change anything.

From browsing different related Q&As, it seems that you cannot use std::function with template functions. Is this the problem here?

Is there a way to make tc1.SetFunction<std::string, double>(some_function);work within testclass1?

Thanks in advance.

UPDATE: I'm trying to wrap my head around the explanation, I think I sort of get it but then---thinking about it---I'm still not 100% sure. Consider the following code:

template <typename...Ts>
using test_tuple = std::tuple<Ts...>;

template <typename...Ts>
void test_func(test_tuple<Ts...> tup)
{
    // Do something
    // e.g., PrintTuple(tup);
}

int main()
{
    test_tuple<std::string, double> tt{"Hi", 3.1459f};
    test_func<std::string>(tt);
}

In that case, only std::string is specified in the parameter pack and the double is deduced (so not all parameters are specified in the pack). But why does it work in this case and I don't get an error?

CodePudding user response:

it seems that you cannot use std::function with template functions

No, the issue has nothing to do with std::function.

The problem is the failed deduction of template parameter pack. You specified some pack arguments, but how does the compiler know if you specified all of them? It's possible to specify some pack arguments and let the compiler deduce the rest. So the compiler still performs deduction.

But std::function<void(std::string*, double*)> can't be deduced from void(*)(std::string*, double*) (and implicit conversions are not considered during deduction). Hence the error:

template argument deduction/substitution failed:
mismatched types 'std::function<void(Ts* ...)>' and 'void (*)(std::string*, double*)'

(note - there is no mention of "type-parameter-0-0" in GCC 11 output, so your compiler might be outdated).

Now, to fix this, we can introduce a non-deduced context to the parameter of SetFunction. Then the substitution will be performed from the provided type arguments.

For example using an identity template:

template<typename T>
struct type_identity {
    using type = T;
};

class testclass1
{
public:
    
    template <typename...Ts>
    using TFunc = std::function<void(Ts*...)>;

    template <typename...Ts>
    void SetFunction(typename type_identity<TFunc<Ts...>>::type tf)
    {
        // Do something
    }
};

(Live demo)

  • Related