10

The following code when built with

clang -Wall main.cpp -o main.o

generates the following diagnostics (after the code):

template <typename F>
void fun(const F& f)
{

}

template <typename F>
void fun(F f)
{

}

double Test(double d) { return d; }

int main(int argc, const char * argv[])
{
    fun(Test);

    return 0;
}

diagnostics:

main.cpp:17:5: error: call to 'fun' is ambiguous
    fun(Test);
    ^~~
main.cpp:2:6: note: candidate function [with F = double (double)]
void fun(const F& f)
     ^
main.cpp:8:6: note: candidate function [with F = double (*)(double)]
void fun(F f)
     ^
1 error generated.

The interesting part is not about the ambiguity error itself (that's not the major concern here). The interesting part is that the template parameter F of the first fun is resolved to be a pure function type of double (double), while the template parameter F of the second fun is resolved to be a more expected double (*)(double) function pointer type, when fun is invoked with a function name solely.

However, when we change the invocation of fun(Test) to be fun(&Test) to explicitly take the function's address (or explicit function pointer), then both fun resolve the template parameter F to be double (*)(double)!

This behavior seems to be a common one for all of Clang and GCC (and Visual Studio 2013).

The question is then: what's the function type template parameter deduction rule for template functions in the forms given in my example code?

PS: if we add another instance of fun to take F* f, then it seems the overloading rule just decides to pick this version, and no ambiguity is reported at all (even though, as I have already stated, the ambiguity is not the biggest concern earlier, but in this last case, I do wonder why the third version is the best match here?)

template <typename F>
void fun(F* f)
{
}
Dejavu
  • 1,299
  • 14
  • 24
  • 1
    Regarding my last "bonus" question, I think it's the exact match, so it's always chosen if present. – Dejavu Mar 12 '14 at 22:06

2 Answers2

5

Probably you have already figured it out, as it has been almost 3 years since you posted that question. But I will give my answer in case you haven't.

The interesting part is that the template parameter F of the first fun is resolved to be a pure function type of double (double), while the template parameter F of the second fun is resolved to be a more expected double (*)(double) function pointer type, when fun is invoked with a function name solely.

First of all, bear in mind that arrays and functions are strange in that an array can implicitly decay into a pointer to its first element and functions can implicitly decay into function pointers. And although syntactically valid, function parameters cannot actually be of array or function type but pointers, meaning function parameters can be written with the type of array or functions, but compilers regard such types as pointers. For example look at the code below:

int val [3]; //type of val is 'int [3]'
int * pval = val; //type of pval is 'int *'
                  //the assignment is correct since val can decay into 'int *'

double foo(double); //type of foo is 'double (double)'
double (*pfoo) (double); // type of pfoo is 'double (*)(double)'
pfoo = foo; //correct since functions can decay into function pointers.

void bar(int x []); // syntax is correct 
                    // but compilers see the type of x as 'int *'

void bar(int x(int));// again syntax is correct
                     // but compilers see the type of x as 'int (*)(int)'

However, things get even stranger when a function parameter has a reference type. A function parameter who has a type of reference to an array/function is regarded to have a type of reference to an array/function, not a pointer type. For instance:

void bar(int (& x)[2]); //type of x is now 'int (&) [2]'
void bar(int (& x)(int)); //type of x is now 'int (&)(int)'

Regarding your first question, since the type of the parameter in the first function of yours (fun(const F& f)) includes a reference, the type of f will be deduced as a reference to a function, when a function is passed as an argument; to be more precise, the deduced type of f will be double (&) (double). On the other hand, as the second function's parameter type does not include a reference (fun(F f)), compilers implicitly deduce the type of f as a function pointer (the deduced type of f will be double (*)(double)), when a function is passed as an argument.

However, when we change the invocation of fun(Test) to be fun(&Test) to explicitly take the function's address (or explicit function pointer), then both fun resolve the template parameter F to be double (*)(double)!

Well, now since you are explicitly passing a function pointer type as an argument (by taking the address of Test), the deduced type of f must have a pointer. However, the reference and the constantness of the first function's parameter is not ignored. When fun(&Test) is run, the deduced type of f for the first function will be double (* const &) (double) and the deduced type of f for the second function will be double (*) (double).

PS: if we add another instance of fun to take F* f, then it seems the overloading rule just decides to pick this version, and no ambiguity is reported at all (even though, as I have already stated, the ambiguity is not the biggest concern earlier, but in this last case, I do wonder why the third version is the best match here?)

(I deleted my prior answer for that part, please refer to below)

EDIT: I gave a very sloppy answer for the question of how come there is no ambiguity any more when a third function (fun(F * f)) is added. Below is a clear answer, I hope.

The rule for resolving which function to pick up in the case of function templates is first to figure out the set of template specializations for a given argument. The reason for this is to eliminate the function templates that result in substitution failure as a candidate. Then based on the conversions from the argument to the parameters, worse matches are eliminated from the candidate pool of non-template functions and the valid template specializations. If a non-template and a template functions are equally good match, the non-template is picked up. If more than one template function are equally good match, then partial ordering rules are employed to eliminate the less specialized function templates. If one shines as the most specialized function template, then it is resolved; on the other hand if neither is more specialized, then the compiler issues an ambiguity error. Needless to say, if there is no valid candidate found, error is issued again.

Now let's point out again the template specializations for the argument Test. As mentioned above, after template type deduction the template specialization of the first function template is void fun(double (&f) (double) ) and that of the second function template is void fun(double (*f) (double) ). Based on the conversions needed from the argument type double (double) to the candidate template functions' parameter types double (&) (double) and double (*) (double) respectively, they both are regarded exact match since only trivial conversions are needed. Therefore, partial ordering rules are employed to distinguish which one is more specialized. It turns out that neither is, so ambiguity error is issued.

When you added the third function template (void fun(F * f)), the template type deduction produces the template specialization as void fun(double (*f)(double). As same before, all of the three template functions are equally good match (in fact they are exact match). Because of that again, partial ordering rules are employed as a last resort and it turns out that the third function template is more specialized and thus it is picked up.

Note on trivial conversions: Although not complete, the following conversions from argument type to parameter type are considered trivial conversion (given a type T):

  1. from T to const T
  2. from T to T &
  3. or from array or function types to their corresponding pointer types (decays that are mentioned at the beginning).

EDIT #2: It seems that I may not be using the correct wordings, so just to be clear what I mean by function template is a template that creates functions and by template function a function that is created by the template.

Community
  • 1
  • 1
Ugur Yilmaz
  • 156
  • 1
  • 7
  • Great answer, except for the very last part: You didn't really explain why having both a by value template and a by non-const pointer template doesn't give an ambiguity, despite both providing a pointer to function specialization. – celtschk Feb 19 '17 at 11:58
  • @celtschk, the last part is elaborated – Ugur Yilmaz Feb 19 '17 at 22:59
  • Thank you, now the answer is perfect. I started a bounty; once the mandatory waiting time has passed (no idea why they require that for a bounty reason that clearly says it is for an existing answer), I'll award it to your answer. – celtschk Feb 20 '17 at 07:49
  • While I won't nitpick on wording choices, there are some important things to note: partial ordering of function templates is the last step in overload resolution, not the first. Doing it first would actually choose a different overload in some cases. It's also important to clarify that partial ordering works on the function templates themselves (it has nothing to do with specialization declarations generated after template argument deduction and substitution), while the generated specializations are essential for the rest of the overload resolution process. – bogdan Feb 20 '17 at 09:22
  • OK, maybe I'll nitpick a little: *trivial conversions* is not a standard term, maybe it would be worth noting that it's just something you're using to explain things here. Also, *exact match* is a larger category in the standard than what you're describing. And since I've started nitpicking and can't stop, there's no *template function* as a standard term (function templates are not functions; they're templates, blueprints for generating functions). There is the term *non-template function* to designate functions that are not function template specializations. – bogdan Feb 20 '17 at 09:34
  • @bogdan sorry if not clear: first the set of function template specializations are found, so that the function templates that result in substitution failure with a given argument are eliminated. Then among the function templates (note that not the template specializations) that resulted in a valid template specializations, partial ordering rules are employed. Then only the template specializations of the more specialized templates are considered for the overload resolution process. I am sure of this order, please refer to pg. 690 of C++ Programing Language, 4th Edition by B. Stroustrup – Ugur Yilmaz Feb 20 '17 at 10:21
  • @bogdan however you may be right about the wording choices. Just to be clear, I use "**function templates**" for templates that create functions and "**template functions**" for functions created from templates. As for the trivial conversion goes, that is what I learned from B. Stroustrup's book of Programming Language, 4th Edition (page 327). And I didn't mention about the promotions, standard conversions and other kind of conversions (which are worse match cases than exact match) since they are not relevant to the case given in the code above. – Ugur Yilmaz Feb 20 '17 at 10:38
  • @bogdan by the way I will be glad if you can inform me about what else is regarded an exact match during the overload resolution. – Ugur Yilmaz Feb 20 '17 at 10:40
  • @J.Matt Regarding partial ordering: you may be sure, but you're wrong. See [\[over.match.best\]/1](http://eel.is/c++draft/over.match.best#1) in the standard. Bjarne may have chosen to explain things like that for some reason, but that's not how it's specified and it's not how compilers implement it. A simple proof: http://melpon.org/wandbox/permlink/YG64fitM9aUXmiRb (the first one that came to mind). For the call `f(a)`, `#2` is chosen, because it's a better match based on ranking conversion sequences on the generated specializations; the process doesn't get as far as partial ordering. – bogdan Feb 20 '17 at 10:55
  • @bogdan finally given your input, I edited my post a bit. I hope it is much clearer. – Ugur Yilmaz Feb 20 '17 at 10:55
  • For `f(b)`, `#1` is chosen because the two generated specializations are identical (`void f(const int&)`), and the process goes all the way down to partial ordering, choosing `#1` because it's more specialized (this part is also proof that `#1` is more specialized, in case you doubt that). – bogdan Feb 20 '17 at 10:56
  • Regarding wording choices, things are indeed clearer after the edits. Regarding what else is an exact match, qualification conversions, (between pointers, arrays and such), for example, are also ranked as exact match; see [\[over.ics.scs\]/3](http://eel.is/c++draft/over.ics.scs#3). Oh, and, sorry, in my comment above, instead of `f(b)` I meant `f(ca)`, of course. – bogdan Feb 20 '17 at 11:03
  • @bogdan Thank you for the example but I think you are wrong because neither is more specialized. Hence they both emerge from the partial ordering rules as equal candidates. After then during the general non-template overload resolution process, although they both are exact matches, `f(const T&)` is regarded better match for `ca` (and `f(T &)` for `a`) due to constant overloading of references and pointer parameter types (please refer to page 384 of C++ Primer Plus, 5th edition by Stephen Prate) – Ugur Yilmaz Feb 20 '17 at 11:22
  • @bogdan Note that if the parameters were not references, the compiler will give redefinition error because only pointer and reference parameter types can be overloaded on constantness. And also, can you see why neither is more specialized? – Ugur Yilmaz Feb 20 '17 at 11:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/136146/discussion-between-bogdan-and-j-matt). – bogdan Feb 20 '17 at 11:26
2

Probably others can explain this better than me, but this is how I understand it (no quotes from the Standard, sorry).

One cannot copy a variable of function type around, so in template <typename F> void fun(F f), the F cannot have function type.

However, a variable of function type can be converted to a pointer to the function type (this is called "decay", like the array-to-pointer conversion), so when matching a function type with template <typename F> void fun(F f), F must be a pointer to a function.

When dealing with a reference to a function type, the function-to-pointer decay cannot happen (i cannot find this in the Standard, but it should be described together with the reference-to-array rules), so when matching template <typename F> void fun(const F& f), F is a function type (and the parameter's type is reference-to-function).

anatolyg
  • 26,506
  • 9
  • 60
  • 134
  • 4
    I think the decay to pointer could still happen with the reference parameter, it's just that it doesn't *have* to. I don't have quotes from the standard either, mind :-) – Cameron Mar 12 '14 at 18:19
  • Makes sense, but I would like to wait for more insights. :-) – Dejavu Mar 12 '14 at 18:22