简体   繁体   中英

C++ function template specialization declarations and template arguments; none vs. <> vs. <type>

When studying function templates, I see specializations declared in different ways:

template<> void f(argtype) {}
template<> void f<>(argtype) {}
template<> void f<argtype>(argtype) {}

... and I wonder about the differences between these. Given the below example with template functions with and without parameter, I have a few questions.

#include <iostream>
#include <typeinfo>

//Function print1 WITH function parameter---------------------------------------------
template<class T>
void print1(T) { std::cout << "Primary template for print1() with type " << typeid(T).name() <<  std::endl; }

template<>
void print1<int>(int) { std::cout << "Specialization for print1<int>(int)" << std::endl; }

//Not allowed, deduced to be the same as print1<int>(int)
/*template<>
void print1<>(int) { std::cout << "Specialization for print1<>(int)" << std::endl; }*/

//Not allowed, deduced to be the same as print1<int>(int)
/*template<>
void print1(int) { std::cout << "Specialization for print1(int)" << std::endl; }*/

//Function print2 WITHOUT function parameter------------------------------------------
/*Not allowed together with print<>(); compiler complains: 
    t2.cpp:29:6: error: template-id 'print2<>' for 'void print2()' does not match any template declaration*/
/*template<class T>
void print2() { std::cout << "Primary template for print2()" << std::endl; }*/

template<class T = short> //Declaration of print2<>() now ok in conjunction with print2<>()
void print2() { std::cout << "Primary template for print2()" << std::endl; }

template<>
void print2<int>() { std::cout << "Specialization for print2<int>()" << std::endl; }

template<>
void print2<>() { std::cout << "Specialization for print2<>()" << std::endl; }

int main() {
    //These three work in the same way, no matter which call method we use, so far so good
    print1(10);
    print1<>(10);
    print1<int>(10);
    print1(true);
    print1<>(true);
    print1<bool>(true);

    print2(); //Triggers print2<>(), a bit unexpectedly, should trigger print2<short>() (primary template)
    print2<>(); //Triggers print2<>(), a bit unexpectedly, should trigger print2<short>() (primary template)
    print2<bool>(); //Triggers print2<bool>() primary template
    print2<short>(); //Triggers print2<>(), should definately trigger primary template for print2()
    print2<int>(); //Triggers print2<int>() specialization
    return 0;
}

outputs:

Specialization for print1<int>(int)
Specialization for print1<int>(int)
Specialization for print1<int>(int)
Primary template for print1() with type b
Primary template for print1() with type b
Primary template for print1() with type b
Specialization for print2<>()
Specialization for print2<>()
Primary template for print2()
Specialization for print2<>()
Specialization for print2<int>()
  • What special meaning is derived from leaving the template specialization argument empty, non-existent or with the specialized type and how does it effect the outcome? It seems that with a function argument, this specification is superfluous and the compiler deduces it no matter how it's specified (with the result that equivalent explicit specifications become unallowed redeclarations).
  • I understand that given a function without parameters, the specialized template argument is needed explicitly in declaration to specify for which instantiation the defined function applies to (since it can't be deduced otherwise). But the meaning seems to imply something more in this case and the "empty" specialization (<>) is triggered in a somewhat unforeseen ways. How come?
  • Why do I have to have a default template parameter when specializing print2 with print2<>() but not without it?

What special meaning is derived from leaving the template specialization argument empty, non-existent or with the specialized type and how does it effect the outcome?

If you do provide a template argument list completely then you're simply explicitly specializing the function template for a given set of template arguments.

If you provide arguments for a (possibly empty) subset of the template parameters then you are explicitly specializing the function template for a set of arguments that has to be deduced . According to [temp.deduct.decl]:

In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations (14.7.2), explicit specializations (14.7.3), and certain friend declarations (14.5.4). […]. In all these cases, P is the type of the function template being considered as a potential match and A is […] the function type from the declaration […].
The deduction is done as described in 14.8.2.5.

If, for the set of function templates so considered, there is either no match or more than one match after partial ordering has been considered (14.5.6.2), deduction fails and, in the declaration cases, the program is ill-formed.

So for every parameter for which no argument was given, or in the case where no list is specified at all, template argument deduction is done for each corresponding parameter of the specialization and its counterpart from the primary template. The process is described in §14.8.2.5 and works just as if we called the primary template with the provided template argument list as the template arguments and objects of the types of the parameters in the specialization as the function arguments.

You should be familiar with the fact that one can call a function template with some of the template arguments specified, eg

template <typename A, typename B> void foo(A, B);

foo(7684, 48.);
foo<int>(7684, 48.);
foo<int, double>(7684, 48.);

That works equivalently for explicit specializations:

template <typename T, typename U>
void foo(T, U) {}

// Both template arguments have to be deduced.
template<> void foo(double, float);

// The *exact* same as above.
// template<> void foo<>(double, float);

// Second template argument has to be deduced by type.
// If we call foo<int>(int(), float()) then the deduced specialization is
// foo<int, float>, thus U=float.
template<> void foo<int>(int, float);

template<> void foo<int, int>(int, int);

This can also be applied to overloads of a function template. In an attempt to find the primary template a specialization is corresponding to, the most specialized one is chosen.

template <typename T, typename U>
void foo(T&, U&) {}

template <typename T, typename U>
void foo(T const&, U&) {}

// Specializes the second overload because it is more specialized.
template <>
void foo(int const&, float&);

Note that while looking for a primary template, the arguments provided (ie not to be deduced) are used to check the resulting function parameter of the primary template against the resulting function parameter of the specialization. They have to be equal.

template <typename T, typename U>
void foo(T&, U&) {}

// Error - no matching primary template found.
template <>
void foo<int, int>(float&, int&);

// Dito:
template <>
void foo<int>(int, int&);

It seems that with a function argument, this specification is superfluous and the compiler deduces it no matter how it's specified (with the result that equivalent explicit specifications become unallowed redeclarations).

Yes, that is indeed the case. Consider that if you specify a template argument invalidly that results in an error:

But the meaning seems to imply something more in this case and the "empty" specialization (<>) is triggered in a somewhat unforeseen ways. How come?

For a call, the template arguments are deduced first. Then the specialization with those template arguments is called.

If you explicitly specialized a function template for this particular specialization, here that is print2<> which is print2<short> , then that explicit specialization is thus called.
In what way is that unforeseen?

Why do I have to have a default template parameter when specializing print2 with print2<>() but not without it?

Because the compiler cannot deduce the argument. If you provide a default argument he doesn't have to deduce it in the first place.

What special meaning is derived from leaving the template specialization argument empty

Missing arguments are deduced if possible; an empty argument list means that all arguments are to be deduced.

non-existent

That means you're declaring the primary template, not an explicit specialisation.

or with the specialized type

That means you're declaring an explicit specialisation for that type.

the "empty" specialization (<>) is triggered in a somewhat unforeseen ways. How come?

In both cases, the template argument is deduced. In print1 , the primary template declares that T is the same type as the function parameter; so it's deduced from the function parameter. In print2 , it's declared with a default type of short , so that is used. So your surprise at seeing print2<> when you think it should be print2<short> is explained: print2<> is print2<short> .

Why do I have to have a default template parameter when specializing print2 with print2<>() but not without it?

If there were neither a default argument nor a function parameters from which to deduce an argument, then it would be impossible to deduce a type for the specialisation, so <> couldn't be used. I don't know what you mean by "without it"; if you mean "without <> ", then you're declaring the primary template, not a specialisation, and the parameter is generic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM