function overload matching template template

2019-04-14 22:55发布

I would expect the last two lines of the first code example to print the same.

The types are deducted as I expect and the the overload resolution is also as I expect. However, if I explicitly type qualify the function call, then I get a different result then when the type is deduced.

The second code example repeats the exercise replacing overload resolution with specialization. In that case everything works as anyone would expect.

Any explanation?

EDIT: I added one more line showing what Karthik was mentioning regarding print<R,int>(r); which I also do not understand.

Code Example 1: (function template overloading)

#include <iostream>
template <typename T>
void print (T i)
{
        std::cout << "simple" << std::endl;
}
template <template<typename> class FF, typename TT>
void print (FF<TT> i)
{
        std::cout << "template" << std::endl;
}
template <typename T1, typename T2>
void print (T1 a)
{
        T2 b;
        std::cout << "two type parameters" << std::endl;
}
template <>
void print<int>(int i)
{
        std::cout << "int" << std::endl;
}
template <typename T>
struct R
{
        T x;
};
int main()
{
        R<int>  r;
        print<int>(1.1);          // ok, prints "int"
        print(1.1);               // ok, prints "simple"
        print<int>(1);            // ok, prints "int"
        print(1);                 // ok, prints "int"
        print(r);                 // ok, prints "template"
        print<int,int>(1);        // ok, prints "two type parameters"
        print<R<int>,int>(r);     // ok, prints "two type parameters"
        print<R<int> >(r);        // (1) ?? why "simple" ??
        print<R,int >(r);         // (2) ?? prints "template", why does it compile at all ??
                                  // gcc 4.6.2 (-std=c++0x) and 4.8.1 (-std=c++11)
                                  // clang++ 3.3.1 same behavior as gcc
}

Code Example 2: (class template specialization).

#include <iostream>

template <typename T>
struct P
{
    static void print (T i)
    {
        std::cout << "simple" << std::endl;
    }
};
template <template<class TT> class FF, typename TT>
struct P <FF<TT> >
{
    static void print (FF<TT> i)
    {
        std::cout << "template" << std::endl;
    }
};
template <>
struct P<int>
{
    static void print(int i)
    {
        std::cout << "int" << std::endl;
    }
};
template <typename T>
struct R
{
    T x;
};
int main()
{
        R<int>  r;
        P<double>::print(1.1);       // ok, prints "simple"
        P<int>::print(1);            // ok, prints "int"
        P<R<int> >::print(r);        // ok, prints "template"
        //P<R,int >::print(r);       // ok, does not compile
}

4条回答
一夜七次
2楼-- · 2019-04-14 23:27

It would print simple because the type R<int> is deduced as int. In your case, you need to pass 2 parameters to explicitley make it deduce as template <template<typename> class FF, typename TT>

查看更多
Lonely孤独者°
3楼-- · 2019-04-14 23:36

This is a guess not an answer, those with the standard at their finger tips can enlighten us all,

But let me take an educated guess

template <template<typename> class FF, typename TT>   //......(1)

TT is a type while FF is considered a template template parameter

template <typename T>    // .....(2)

T is a type

Now when Explicitly specifying the type, R<int>, it is a concrete type, thus (2) is picked.

When asking it to infer, I guess the compiler tries both options and (1) fits closer (or more specific), and thus that is chosen.

When explicitly specifying <R,int>, ofcourse we use the exact signature of (1) and that is why that one is picked.

+1 to the question, I would not have expected this either.

Perhaps some useful information can be found here

查看更多
We Are One
4楼-- · 2019-04-14 23:39

Well, let's look at what the compiler thinks of each of these.

template <typename T> void print (T i); // (1)
template <template<typename> class FF, typename TT> void print (FF<TT> i); // (2)
template <typename T1, typename T2> void print (T1 a); // (3)
template <> void print<int>(int i); // (4)

Ok, some preliminaries: we have three function templates here that overload each other (1, 2, and 3), and 4 is a specialization of 1.

All three overloads have a single function parameter. In addition, the functions have template parameters:

1 has a single type template parameter that can be deduced from the function parameter.

2 has a template template parameter and a type template parameter, both of which can be deduced from the function parameter.

3 has two type template parameters, only the first of which can be deduced (making the deduction useless).

Now let's look at the calls. When there are explicit template arguments, the compiler will always "pre-filter" the overloads for those functions that can be instantiated that way.

    print<int>(1.1);          // ok, prints "int"

One explicit type template argument. 1 matches. 2 doesn't match, because the first argument isn't a template. 3 matches, fixing T1 to int; however, T2 cannot be deduced, so it falls away, too. 1 is chosen with the parameter T being int. This matches the specialization 4.

    print(1.1);               // ok, prints "simple"

No explicit template arguments. Deduction starts; the argument type is double. 1 matches; T is double. 2 requires the pattern FF<TT>, and double doesn't match that, so failure. 3 can deduce T1 to double, but has nothing for T2, so it fails too. 1 is chosen. The specialization doesn't match.

    print<int>(1);            // ok, prints "int"

This is identical to the first case, except that during final overload resolution, an implicit conversion happens.

    print(1);                 // ok, prints "int"

This is identical to the second case, except that the deduced type is int (still doesn't match FF<TT>), so the specialization matches.

    print(r);                 // ok, prints "template"

Deduction gives the following results: 1 matches, with T = R<int>. For 2, R<int> matches the pattern FF<TT>, so it is viable, with FF = R and TT = int. 3, as usual, doesn't know what to do with T2. Overload resolution gets identical sequences for 1 and 2 (identity), so partial function template ordering resolves the ambiguity: 2 is more specialized than 1 and is chosen.

    print<int,int>(1);        // ok, prints "two type parameters"

Two explicit type template arguments. 1 only accepts one. 2 wants a template as the first argument. 3 is left.

    print<R<int>,int>(r);     // ok, prints "two type parameters"

This is identical to the previous case. The first argument is R<int> instead of int, but that's still just a type and 2 doesn't like it.

    print<R<int> >(r);        // (1) ?? why "simple" ??

This is identical to the first and third cases. We have one explicit type template argument. 3 fails to deduce T2, 2 wants a template as the first argument, so 1 is the only choice. R<int> is a type, not a template.

    print<R,int >(r);         // (2) ?? prints "template",

Here, we have two explicit template arguments, the first a template, the second a type. 1 only accepts one argument. 3 wants a type for its first template parameter. 2 is happy to take the template for its first and the type for its second parameter.

The key lessons here are:

  • Explicit template arguments are matched to the template parameters before any deduction or overload resolution happens, so only some functions match in the first place.
  • 1 and 2 are overloads, and overload resolution happens separately for them. It would be different if 2 was a specialization of 1, but partial function template specialization doesn't exist.
  • It's only a template until you give it arguments. An instantiated function template is a function. An instantiated class template is a class (and thus a type). There is no difference between the instantiation of a class template and a non-template class, except for the pattern matching that argument deduction and partial specialization do.

Edit: To answer the expanded question.

When I replace function overloading with template specialization, then template pattern matching works as I would have expected. I have some trouble believing that the pattern matching rules also differ between classes and functions.

This is a matter of perspective. There are no pattern matching rules for classes and functions, so you can't say that they differ or not. There are pattern matching rules for partial specialization and for template argument deduction. These are actually the same; the section on partial specialization (14.5.5) refers to the section on function template argument deduction (14.8.2).

So the pattern matching rules are the same.

However, argument deduction only applies to functions (there's no argument deduction for class templates, at least not yet), while partial specialization only applies to classes (you can't partially specialize functions). This is the key difference between functions and classses: in your first example, you have two function templates:

template <typename T> void print(T i);
template <template <typename> class FF, typename TT> void print(FF<TT> i);

These are two different templates. They are completely independent. It's up to the complicated rules and interactions of explicit parameter passing, argument deduction, and overload resolution to determine which one is meant in any given invocation. However, and this is important, each can exist without the other. In other words, imagine you had only one function:

template <template <typename> class FF, typename TT> void something_else(FF<TT> i);

Would you then be surprised that something_else<R, int>(r); is valid? You have a template with two parameters, and you pass two arguments. The existence of another template with just one argument doesn't change that!

This is important, so I'll repeat it: Two function templates, even if they have the same name, are completely independent templates.

Not so with classes. If you try the same thing with classes, the compiler will complain:

template <typename T> class Q {};
template <template <typename> class FF, typename TT> class Q {};

Clang says:

redef.cc:2:5: error: too many template parameters in template redeclaration

You cannot have two class templates with the same name. The compiler thinks you want to declare the old Q again, and complains that the template parameter lists don't match.

The only thing you can do with class templates is specialize them, as you did in your second example:

template <typename T> class P {};
template <template <typename> class FF, typename TT> class P<FF<TT>> {};

But note that these are not independent templates. They are not even the same thing. The first is a class template, whereas the second is a class template partial specialization. The second is completely dependent on the first; removing the primary template means the specialization no longer compiles:

redef.cc:2:64: error: explicit specialization of non-template class 'P'    

Unlike the overloaded function templates, a class template partial specialization is not an entity the user can reference. For the user, there is one template, P, and it has one template parameter. The specialization will match if this one parameter takes a specific form, but unlike the second function template in the first example, the specialization is not an independent template with two parameters.

This is why P<R<int>>::print(r) compiles and works: P has one parameter, and R<int> is passed for it. The partial specialization matches the pattern and is therefore chosen. But P<R, int>::print(r) does not work: P only has one template parameter, and here you're trying to pass two. The specialization is not its own entity and therefore is not considered.

But the function templates are all independent, full templates. Only the full specialization template <> void print<int>(int i) is not.

So to sum up:

  • Function templates can be fully specialized or overloaded. Overloaded function templates are fully independent: when you want to know what arguments you can or should explicitly supply, look at all their parameter lists in turn.
  • Avoid specializing function templates. It interacts with overloaded function templates in weird ways, and you can end up with a function different from what you expected being called. Just overload function templates, with other function templates and plain functions. The partial ordering rules are intuitive; just remember that when in doubt, a plain function is chosen over a template.
  • Class templates can be fully or partially specialized, but not overloaded. Specializations are not independent templates; when instantiating a class template, you always have to go to the primary template for the parameter list.
  • The matching rules for choosing a partial specialization and for deducing template arguments from function arguments are the same; however, when you explicitly pass template arguments to a function template, deduction is not performed for these parameters.
查看更多
劳资没心,怎么记你
5楼-- · 2019-04-14 23:53

The line print< R<int> >(r); looks like a single-template-parameter print (it can't guess what you want) so it calls the single-template-parameter function with T = R<int>.

print< R,int >(r); calls for a two-template-parameter function, of which there is only one version (template). Luckily R is a template that can be instantiated with int so it compiles.

查看更多
登录 后发表回答