Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ function type template parameter deduction rule

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)
{
}
like image 814
Dejavu Avatar asked Mar 12 '14 17:03

Dejavu


People also ask

Can we pass Nontype parameters to templates?

Template classes and functions can make use of another kind of template parameter known as a non-type parameter. A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument.

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

What is the validity of template parameters?

3. What is the validity of template parameters? Explanation: Template parameters are valid inside a block only i.e. they have block scope.

How will you restrict the template for a specific datatype?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.


1 Answers

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.

like image 80
Ugur Yilmaz Avatar answered Oct 26 '22 05:10

Ugur Yilmaz