Why specialization is not called, when <> use?

70 Views Asked by At

In the first segment code all is ok. 2 template functions make an overload set, second wins and instanced by specialization.

template <typename T> void foo(T) {std::cout << "1";}
template <typename T> void foo(T*) {std::cout << "2";}
template <> void foo(int*) {std::cout << "3";}

int x;
foo(&x);

But if I make a little changes, it's behaviour is out of my mind:

template <typename T> void foo(T) {std::cout << "1";}
template <typename T> void foo(T*) {std::cout << "2";}
template <> void foo<int*>(int*) {std::cout << "3";}

int x;
foo(&x);

Suddenly specialization is relate to first function only. Explain, how it works, please

I have tried some test and cannot fuuly understand how it works after all

1

There are 1 best solutions below

0
Yakk - Adam Nevraumont On

With template classes, we have specialization and partial specialization of classes.

With template functions, we have full specialization and overloading of functions.

These are often used to solve similar problems, but they work differently.

template <typename T> void foo(T) {std::cout << "1";}
template <typename T> void foo(T*) {std::cout << "2";}
template <> void foo(int*) {std::cout << "3";}

here, C++ tries to do magic for you. Your template<> void foo(int*) is a specialization, and you have asked for the language to auto-detect which function template it is a specialization for. It picks #2. The process which it picks it involves faking a function call and then following a bunch of obtuse rules. It usually gets it right.

template <typename T> void foo(T) {std::cout << "1";}
template <typename T> void foo(T*) {std::cout << "2";}
template <> void foo<int*>(int*) {std::cout << "3";}

here you have said "no, I will tell you which one I'll use!".

Within the foo<int*> you have said "this is going to specialize some foo function template, and the first argument will be int*.

What more, the arguments will be (int*).

Let's look at our options!

For #1, we have foo<int*>(int*). If we feed T=int* into it:

template <typename T=int*> void foo(T=int*) {std::cout << "1";}

yay! It works, possible case.

Now lets try the second:

template <typename T=int*> void foo(T=int* *) {std::cout << "2";}

oh oh, here we have foo<int*>(int**). That doesn't match our specialization function signature. So we aren't specializing this.

Thus, the compiler picks the first one as the only template function you could possibly be specialization.

...

Going back to your first attempt where you left it up to the compiler to do magic, it ends up seeing both as plausible, but the foo(T*) is more specialized because anything foo(T*) could match, so could foo(T), but the other way around doesn't work. And a bunch of logic in the standard uses concepts like more specialized (or, rather, not less specialized) to order choices.

...

As a general rule, I'm going to advise you simply avoid function template specialization. Because it interacts with function template overloading in frankly unintuitive ways, it is usually better to dispatch to non-overlapping overloads before doing anything fancy like that.

Ie:

template <typename T> void foo(T) {std::cout << "1";}
template <typename T> void foo(T*) {std::cout << "2";}
void foo(int*) {std::cout << "3";}

here, we create a foo(int*) overload.

Alternatively:

template <typename T> void foo(T) {std::cout << "1";}

namespace details {
  template<class T> void foo_ptr(T*){ std::cout << "2"; }
  void foo_ptr(int*){ std::cout << "3"; }
}
template <typename T> void foo(T*) {return details::foo_ptr(t);}

here we permit static_cast<void(*)(int*)>(&foo<int>) to be a pointer to the int specialized implementation, but we never do any specialization of function templates.

Fully understanding the rules of function template specialization is hard. You can have heuristics, and trust that it mostly works, but fully understanding it requires some serious study. So to avoid having to do that study for a skill that is honestly not that useful, just don't use function template specializations.

With overloading, especially with modern C++ requires clauses and constexpr if, you can do almost everything you want to do without using it.