10

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

Holt
  • 36,600
  • 7
  • 92
  • 139
nshibalov
  • 103
  • 5

2 Answers2

8

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.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • @Aconcagua I don't think you are correct. [This](https://msdn.microsoft.com/en-us/library/7y5ca42y.aspx) for example says that "A member function is instantiated when it is called". The compiler fully defines the class, but that only goes so far as to spell out the member function signatures. – Max Langhof Jun 26 '18 at 14:14
  • Hm, the "exact terminology" problem... Could we say the *signature* is already instantiated? Point is: With the class being instantiated the member function is fixed (which is what I was trying to emphasize stronger...). There won't be any template deduction any more, so conversion is legal. For understanding, it does not really matter if code is generated already when the class is instantiated or only when the function is called the first time. – Aconcagua Jun 26 '18 at 14:28
  • I mean, although technically correct, I am feeling somewhat uncomfortable with the term "instantiate", as in the second case we instantiate the function as well (this time including signature), which might be a source for misunderstanding... – Aconcagua Jun 26 '18 at 14:45
  • I actually tried to cover that exact distinction in the answer: "This means the function **declaration** for its `add` member is generated" and "the compiler just instantiates the member function template **definition**". I don't know if declare/define are the correct terms when referring to template instantiation but I tried to make it as clear as I could. Although you are probably right that the actual code generation is of little relevance to the function resolution. – Max Langhof Jun 26 '18 at 14:49
  • The last sentence of your first paragraph doesn't make sense: you don't have a member function template, it's just a simple member function. You terminology is fine though, although you really don't want to use language lawyer terminology, as it is a bit different then the "normal" terms. ;) – Rakete1111 Jun 26 '18 at 15:17
  • "declaration vs. definition": I personally could live with, but one has to read carefully/attentively... My personal try: *"the compiler just uses [there's certainly a better word...] the already completed/ready/[what's the best word here?] function declaration."* – Aconcagua Jun 26 '18 at 15:20
  • @Max "the compiler just instantiates the member function template definition" no? – Rakete1111 Jun 26 '18 at 15:23
  • @Rakete1111 Oh derp, paragraph not sentence. My bad. Anyway, I stuck with the terminology that I saw used [here](http://b.atch.se/posts/non-constant-constant-expressions/#template-instantiation), which specifically talks about _instantiation_ of _declarations_ and _definitions_. In fact, it covers the exact situation here very concisely: _"The definition of a member of a class template specialization is implicitly instantiated when it is required, but not sooner."_ (I know that the author simplified many things, but I don't see myself out-explaining that blog post). Fixed what you pointed out. – Max Langhof Jun 26 '18 at 15:30
  • @Aconcagua See the [source](http://b.atch.se/posts/non-constant-constant-expressions/#template-instantiation) I added. I tried to use the same terminology (from memory) as that author, who seems to know this stuff well enough to make `constexpr` not `constexpr`. – Max Langhof Jun 26 '18 at 15:35
  • @Max Yes the function of the template class is instantiated, but it's not a template, like the author says but you don't :) – Rakete1111 Jun 26 '18 at 15:35
  • @MaxLanghof We three are rather advanced C++ specialists, and see how long the discussion has grown already... Now imagine a beginner is reading this last sentence - it's quite likely that he/she gets irritated and possibly onto the wrong path. That's why I'd rather avoid the term "instantiate" (although technically correct, apart from the function not being template any more) there... – Aconcagua Jun 27 '18 at 08:49
2

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
lubgr
  • 37,368
  • 3
  • 66
  • 117
  • 3
    Unfortunately your reason is not correct, at least partially. The problem is a function pointer is not a `std::function`. Because it isn't the compiler will not try to convert it to one to determine the template types. Basic rule: There are no conversions performed in template argument deduction. – NathanOliver Jun 26 '18 at 12:51