Differences between template specialization and ov

2019-01-17 01:02发布

So, I know that there is a difference between these two tidbits of code:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

template <>
int inc(const int& t)
{
    return t + 1;
}

and

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 1;
}

I am confused as to what the functional differences between these two are. Can someone show some situations where these snippits act differently from each other?

5条回答
Anthone
2楼-- · 2019-01-17 01:27

I can only think of a few differences - here are some examples that don't necessarily cause harm (i think). I'm omitting definitions to keep it terse

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
template <> int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses explicit specialization

// --- against ---

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses template

That is because specializations are not found by name lookup, but by argument matching, so a using declaration will automatically consider a later introduced specialization.

Then, you of course cannot partially specialize function templates. Overloading however accomplishes something very similar by partial ordering (using different types now, to make my point)

template <typename T> void f(T t); // called for non-pointers
template <typename T> void f(T *t); // called for pointers.

int a;
void e() {
  f(a); // calls the non-pointer version
  f(&a); // calls the pointer version
}

That wouldn't be possible with function template explicit specialization. Another example is when references are involved, which causes template argument deduction to look for an exact match of the types involved (modulo base/derived class relationships and constness):

template<typename T> void f(T const &);
template<> void f(int * const &);

template<typename T> void g(T const &);
void g(int * const &);

int a[5];
void e() {
  // calls the primary template, not the explicit specialization
  // because `T` is `int[5]`, not `int *`
  f(a);

  // calls the function, not the template, because the function is an
  // exact match too (pointer conversion isn't costly enough), and it's 
  // preferred. 
  g(a);
}

I recommend you to always use overloading, because it's richer (allows something like partial specialization would allow), and in addition you can place function in whatever namespace you want (although then it's not strictly overloading anymore). For example, instead of having to specialize std::swap in the std:: namespace, you can place your swap overload in your own namespace and make it callable by ADL.

Whatever you do, never mix specialization and overloading, it will be a hell of a mess like this article points out. The Standard has a lovely paragraph about it

The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member class templates of class templates, member function templates of class templates, member functions of member templates of class templates, member functions of member templates of non-template classes, member function templates of member classes of class templates, etc., and the placement of partial specialization declarations of class templates, member class templates of non-template classes, member class templates of class templates, etc., can affect whether a program is well-formed according to the relative positioning of the explicit specialization declarations and their points of instantiation in the translation unit as specified above and below. When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.

查看更多
放荡不羁爱自由
3楼-- · 2019-01-17 01:30

Template specialization is more generic than just overloading. You can specialize things like classes rather than just simple functions. Overloading only applies to functions.

UPDATE: To clarify more per AraK's comment, you are really comparing apples and oranges here. Function overloading is used to introduce the ability to have different functions share a single name, if they have different signatures. Template specialization is used to define a specific code snippet for a specific type parameter. You can't have a template specialization if you don't have a template. If you remove the first piece of code that declares the generic template, you'll receive a compile time error if you try to use template specialization.

So, the goal of template specialization is pretty different from a function overload. They just happen to behave similarly in your example while they are fundamentally different.

If you provide an overload, you are declaring an independent method that happens to have the same name. You are not preventing the template to be used with the specific type parameter. To demonstrate this fact, try:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 42;
}

#include <iostream>
int main() {
   int x = 0;
   x = inc<int>(x);
   std::cout << "Template: " << x << std::endl; // prints 1.

   x = 0;
   x = inc(x);
   std::cout << "Overload: " << x << std::endl; // prints 42.
}

As you can see, in this example, there are two distinct inc functions for int values: inc(const int&) and inc<int>(const int&). You couldn't expand the generic template using int if you had used template specialization.

查看更多
我想做一个坏孩纸
4楼-- · 2019-01-17 01:30

AFAIK there is no functional difference. All I can add is that if you have both a template function specialisation and an ordinary function defined then there is no overload ambiguity as the ordinary function is favoured.

查看更多
forever°为你锁心
5楼-- · 2019-01-17 01:34

Just to elaborate on the first point mentioned by litb in his answer. Specializations are only checked once overload resolution has actually selected a primary template. The result can lead to some surprises where a function is overloaded and has explicit specializations:

template <typename T> void foo (T);  // Primary #1
template <> void foo<int*> (int*);   // Specialization of #1

template <typename T> void foo (T*); // Primary #2

void bar (int * i)
{
  foo(i);
}

When choosing which function to call, the following steps take place:

  1. Name lookup finds both primary templates.
  2. Each template is specialized and overload resolution attempts to select a best function based on conversions between the arguments and parameters.
  3. In thise case, there is no difference in the quality of the conversions.
  4. Partial ordering rules are then used to select the most specialized template. In this case that is the second parimary "foo(T*)".

Only after these steps, when the best function has been selected will explicit specializations of the selected function be considered. (In this case primary #2 has none so none are considered).

The only way to call the above explicit specialization here, is to actually use explicit template arguments in the call:

void bar (int * i)
{
  foo<int*> (i);  // expliit template argument forces use of primary #1
}

A good rule of thumb is to try to avoid having overloads that are also explicily specialized, as the resulting rules are pretty complex.

查看更多
混吃等死
6楼-- · 2019-01-17 01:36

One such example:

#include <cstdio>

template <class T>
void foo(T )
{
    puts("T");
}

//template <>
void foo(int*)
{
    puts("int*");
}

template <class T>
void foo(T*)
{
    puts("T*");
}

int main()
{
    int* a;
    foo(a);
}

It is actually suggested that you use non-template overloads for functions and leave specialization for classes. It is discussed at greater length in Why Not Specialize Function Templates?

查看更多
登录 后发表回答