Kris
Kris

Reputation: 23

Why does std::function not work with function templates?

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?

Upvotes: 2

Views: 504

Answers (1)

rustyx
rustyx

Reputation: 85361

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 the template parameter pack. Normally, when you specify all template arguments explicitly, no deduction is performed.

    tc1.SetFunction<std::string, double>(some_function); // no deduction needed, right?

But in case of a pack, a deduction is always performed. It's because it's possible to specify some pack arguments and let the compiler deduce the rest, and there's no way to know if you specified only some or all the arguments.

But std::function<void(std::string*, double*)> can't be deduced from void(*)(std::string*, double*) (and user-defined 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)


With regard to the update and the follow-up question - test_tuple parameters are happily deduced from a test_tuple argument - there's no conversion involved, it's just pattern matching. But std::function is a class, it can be constructed from a function pointer, but these are different types, so one cannot be deduced from the other.

Upvotes: 2

Related Questions