Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)'

like image 547
nshibalov Avatar asked Jun 26 '18 11:06

nshibalov


People also ask

What is a template argument deduction?

Template argument deduction is also performed when the name of a class template is used as the type of an object being constructed: Template argument deduction for class templates takes place in declarations and in explicit cast expressions; see class template argument deduction for details.

Can a template parameter be deduced from a default argument?

Type template parameter cannot be deduced from the type of a function default argument: Deduction of template template parameter can use the type used in the template specialization used in the function call: Besides function calls and operator expressions, template argument deduction is used in the following situations:

What is the use of function templates in C++?

Function templates allow writing a single definition that can handle multiple different types. It is a very powerful form of C++’s static polymorphism. When instantiating a class template, we have to pass in the types explictly (at least until C++17): But when instantiating a function template, the compiler can often figure the types out:

Can constructor arguments be deduced from class template arguments?

However, sometimes constructors themselves are templated, which breaks the connection that CTAD relies on. In those cases, the author of the class template can provide “deduction guides” that tell the compiler how to deduce class template arguments from constructor arguments.


2 Answers

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.

like image 161
Max Langhof Avatar answered Oct 19 '22 18:10

Max Langhof


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
like image 27
lubgr Avatar answered Oct 19 '22 17:10

lubgr