nshibalov
nshibalov

Reputation: 103

Function template argument deduction (class vs funtion template)

Could you help me understand why the argument deduction works for the class template and does not work for the function template?

If I understand correctly, the class template defines a function, so when I call it is possible for the compiler to make an implicit cast, but in case of the function template, there is no function definition at the moment, so implicit cast not happening.

But I don't understand why the compiler can't create function definition and then apply implicit cast?

#include <functional>

template<typename ...ARGS>
class Test1
{
public:
    void add(const std::function<void(ARGS...)>&) {}
};

class Test2
{
public:
    template<typename ...ARGS>
    void add(const std::function<void(ARGS...)>&) {}
};

void func(int) {}

int main()
{
    Test1<int> test1;
    test1.add(func);

    Test2 test2;
    test2.add<int>(func);
}

The error is:

In function 'int main()':

   25:24: error: no matching function for call to 'Test2::add(void (&)(int))'

   25:24: note: candidate is:

   14:10: note: template void Test2::add(const std::function&)

   14:10: note: template argument deduction/substitution failed:

   25:24: note: mismatched types 'const std::function' and 'void(int)'

Upvotes: 10

Views: 513

Answers (2)

Max Langhof
Max Langhof

Reputation: 23681

In the first case, you are explicitly instantiating the class template Test1. This means the function declaration for its add member is generated with the signature add(const std::function<void(int)>&). When the compiler subsequently tries to resolve test1.add(func), there is only that one candidate. Since std::function<void(int)> can be implicitly constructed from a void(int) function, the signatures match, the compiler just instantiates the member function definition and everything is good.

In the second case, the compiler has to perform template argument deduction/substitution to see if it can "use" the add template. You may think that specifying int would nail down the template parameters so that no deduction is necessary, but that is not the case: It could be that you mean to partially specify template arguments, see for example here. In other words, you might be trying to instantiate the function template with more parameters than you specified explicitly, at least the compiler doesn't know if you do. So it still has to try and match the types of std::function<void(ARGS...)> (or more precisely, std::function<void(int, ...)> and void(int), which it can't, because implicit conversions are not considered for the deduction.

In short: Specifying explicit template arguments does not prevent template parameter deduction for variadic function templates.

Note: I am not 100% firm with the exact terminology, any language lawyering to correct me is appreciated!

Edit: I am basing this primarily on what I read here.

Upvotes: 8

lubgr
lubgr

Reputation: 38267

My original reasoning for why the following snippets work was wrong, but as @NathanOliver helped me out (see below), here is the revised explanation: During template argument deduction, no type conversions are performed. Passing a function pointer to a function that takes a std::function argument requires such a conversion. To circumvent this issue, you can call the method like this

test2.add(std::function<void(int)>(func));

or adjust the definition of Test2 to

class Test2
{
    template<typename ...ARGS>
    void add(void(*)(ARGS...)) {}
}

which works together with the original call

test2.add<int>(func);

In both examples, no conversion is necessary. The call to Test1::add worked out because the template type deduction has been performed before calling the method, hence the conversion could take place.

Note also that the same issue arises when Test2 is declared with one single template parameter,

class Test2
{
    template<typename T>
    void add(const std::function<void(T)>&) {}
}

with the following uses cases on the caller's side:

test2.add<int>(func); // Conversion ok, function template specified
test2.add(std::function<void(int)>(func)); // Type deduction, no conversion
test2.add(func); // Error, conversion AND type deduction

Upvotes: 2

Related Questions