13

The question is pretty simple. Is there any difference between:

template <typename T>
T add(T a, T b)
{
    return a + b;
}

template <>
int add<int>(int a, int b)
{
    return a + b; //no reason to specialize, but still...
}

And:

template <typename T>
T add(T a, T b)
{
    return a + b;
}

int add(int a, int b)
{
    return a + b; //no reason to overload, but still...
}

They seem to be the same.

DarkAtom
  • 2,589
  • 1
  • 11
  • 27
  • I'm not entirely sure myself, would the second one not throw a compiler error over ambiguity if both `add` definitions are in the same namespace? – Romen Sep 17 '19 at 21:31
  • @Romen not sure about what the standard has to say, but on GCC both the specialization and the overload compile fine. – DarkAtom Sep 17 '19 at 21:35
  • I tried it out on Visual Studio. No compiler errors are thrown but Visual Studio uses the non-template version when you call `int a = add(1,2);` in your second version. In the first version it obviously uses the specialized case. So I think they are the same! – Romen Sep 17 '19 at 21:36
  • @Romen same on GCC. This ambiguity is what actually made me ask the question – DarkAtom Sep 17 '19 at 21:36
  • @DarkAtom The ambiguity is settled by the rule that if a template exactly matches the signature of a non template, the non template wins in overload resolution. – NathanOliver Sep 17 '19 at 21:44
  • Possible duplicate of [Template Specialization VS Function Overloading](https://stackoverflow.com/questions/7108033/template-specialization-vs-function-overloading) – xskxzr Sep 18 '19 at 06:51

4 Answers4

4

Other than differences in how overload resolution proceeds, a template specialization locks you into a signature. Meaning you can't do this:

template <>
long add<int>(int a, int b)
// ^- the return type is long , not int
{
    return a + b; //no reason to specialize, but still...
}

A compiler will complain about the changed return type, since the declaration (and function signature) is determined by the primary template. A compiler doesn't even consider the specialization when doing overload resolution. It just instantiates a declaration from the primary, and that declaration must match what it ends up calling.

So overloads are more flexible in how you can define them. And there is no ambiguity for the compiler. The overload resolution mechanism favors a non-template over a template generated function if their argument types match. More often than not it's easier to overload than to specialize a function template.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • It is actually possible to do this by using a dedicated type trait template class like `template struct result { using type = T; };` with specialization for `int` case `template<> struct result { using type = long; };` as a returned type. Changing return type alone for particular `T` won't even require writing specialization for function. – user7860670 Sep 17 '19 at 21:45
  • @VTT - Yes. Which just goes towards C++ being overly expert friendly. Compared to the simplicitly of a plain overload, I don't think I'd ever propose a trait unless I had no choice. – StoryTeller - Unslander Monica Sep 17 '19 at 21:47
3

Fist variant is simpler because compiler will have to collect and then choose from set of candidates containing only a single item add<int>(int, int) specialization. While second variant will cause compiler to perform some extra job by collecting a set of candidates containing two items - add<int>(int, int) and add(int, int) and choose between them using fancy function overload ranking algorithm.

user7860670
  • 35,849
  • 4
  • 58
  • 84
3

It makes no difference in this case, but there are cases where it will. Lets look at this sample code

template<class T> // (a) 
void f(T);

template<>        // (b)
void f<>(int*);

template<class T> // (c)
void f(T*);

int main()
{
    int *p; 
    f(p);
}

When we call f(p);, what function would you expect to be called? Most people would go with b, and they would be wrong. The reason for this is because specializations do not apply to overload resolution. They are a special recipe for a template that tells the compiler that if you deduce this type, then stamp out the template this way instead.

So what the compiler does is go through overload resolution and says I can call a with T being int*, or I can call c with T being int. Since the latter is more specialized (T is narrower), c wins. This can be very surprising but it makes a lot of sense once you ignore the specialization and just consider the overloads.

Walter E. Brown has a very in depth video about templates and specialization called “C++ Function Templates: How Do They Really Work?” that you should consider watching. It is an hour long, but it is very detailed.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
2

In "simple" cases, they would behave identically, but they are some cases where they differ.

A trivial difference, is the explicit call add<int> which will call the specialization and not the overload.

Other differences happens for overload resolution, a call like:

add(42, 4.2f);

will fail with the template specialization (int and float, so T cannot be deduced)

whereas, overload is valid and will convert the float to int.

Jarod42
  • 203,559
  • 14
  • 181
  • 302