Function Template Specialization Importance and Necessity

Function template specialization importance and necessity

Basically the idea is that you can write templates that behave in a generic way for the general case, but can still handle special cases. One example of where specialization is used is in std::vector. std::vector<bool> is a specialization that packs the bool elements such that they only use one bit per element, not one byte. std::vector<T> works like a normal dynamic array for all other types.

The more advanced use for specialization is metaprogramming. For example, here's an example (from Wikipedia) of how to use template specialization to compute factorials at compile time.

template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0>
{
enum { value = 1 };
};

C++ - What is the purpose of function template specialization? When to use it?

The main difference is that in the first case you are providing the compiler with an implementation for the particular type, while in the second you are providing an unrelated non-templated function.

If you always let the compiler infer the types, non-templated functions will be preferred by the compiler over a template, and the compiler will call the free function instead of the template, so providing a non-templated function that matches the arguments will have the same effect of specializations in most cases.

On the other hand, if at any place you provide the template argument (instead of letting the compiler infer), then it will just call the generic template and probably produce unexpected results:

template <typename T> void f(T) { 
std::cout << "generic" << std::endl;
}
void f(int) {
std::cout << "f(int)" << std::endl;
}
int main() {
int x = 0;
double d = 0.0;
f(d); // generic
f(x); // f(int)
f<int>(x); // generic !! maybe not what you want
f<int>(d); // generic (same as above)
}

If you had provided an specialization for int of the template, the last two calls would call that specialization and not the generic template.

Specialize template function to return vector

You can't partially specialize functions. You can overload them though, but the way of doing it is not obvious, since your function doesn't take any parameters.

First, you need a way to check if a type is a std::vector<??>:

template <typename T> struct IsVector : std::false_type {};
template <typename ...P> struct IsVector<std::vector<P...>> : std::true_type {};

Then you can plug it into requires:

template <typename T>
T Read()
{
// Generic overload
}

template <typename T> requires IsVector<T>::value
T Read()
{
// Vector overload
}

Alternatively, you could have a single function, with if constexpr (IsVector<T>::value) inside.

Class Template Specialization vs. Function Overloading

If the only thing that behaves differently is a single function, then you don't have to specialize the whole class, you can just specialize that function. I'm not sure if there's syntax to do this when the function is defined within the body of the class, but if you define the function externally, then you can do it like this:

template <class T>
class X
{
void f();
};

template <class T>
void X<T>::f()
{
// general code
}

template<>
void X<std::string>::f()
{
// specialized code
}

For multiple template parameters

template<int K, typename T> class X;
template<int K, typename T> void friend_func(X<K,T> &);

template<int K, typename T>
class X
{
public:
void class_func();
friend void friend_func<>(X &);
};

template<int K, typename T>
void X<K,T>::class_func()
{
friend_func(*this);
}

template<int K, typename T>
void friend_func(X<K,T> & x)
{
// non specialized version
}

template<int K>
void friend_func(X<K,std::string> & x)
{
// specialized version
}

multiple definition of template specialization when using different objects

Intuitively, when you fully specialize something, it doesn't depend on a template parameter any more -- so unless you make the specialization inline, you need to put it in a .cpp file instead of a .h or you end up violating the one definition rule as David says. Note that when you partially specialize templates, the partial specializations do still depend on one or more template parameters, so they still go in a .h file.

C++ specialization of template function inside template class

So, I'm taking a different approach to answering your question. I'm going to start from something that sort of does what you want, and works. And then maybe we can figure out how to permute it into something closer to what you really want:

#include <string>
#include <iostream>

int getIntThing(const ::std::string ¶m);

template <typename returnT>
returnT getThingFree(const ::std::string ¶m);

template <>
int getThingFree<int>(const ::std::string ¶m)
{
return getIntThing(param);
}

// More specialized definitions of getAThing() for other types/classes
// go here...

template <class c1> class X {
public:
template<typename returnT> returnT getAThing(std::string param);
static std::string getName();
private:
c1 theData;
};

// This works ok...
template <class c1> std::string X<c1>::getName() {
return c1::getName();
}

// This also works, but it would be nice if I could explicitly specialize
// this instead of having to explicitly specialize getThingFree.
template <class c1>
template <class RT>
RT X<c1>::getAThing(std::string param) {
// Some function that crunches on param and returns an RT.
// Gosh, wouldn't it be nice if I didn't have to redirect through
// this free function?
return getThingFree<RT>(param);
}

class Y {
public:
static std::string getName() { return "Y"; }
};

int main(int argc, char* argv[])
{
using ::std::cout;
X<Y> tester;
int anIntThing = tester.getAThing<int>(std::string("param"));
cout << "Name: " << tester.getName() << '\n';
cout << "An int thing: " << anIntThing << '\n';
}

Here is another idea that sort of works, and isn't exactly what you want, but is closer. I think you've thought of it yourself. It's also rather ugly in the way it uses type deduction.

#include <string>
#include <iostream>

template <class c1> class X;

int getIntThing(const ::std::string ¶m)
{
return param.size();
}

// You can partially specialize this, but only for the class, or the
// class and return type. You cannot partially specialize this for
// just the return type. OTOH, specializations will be able to access
// private or protected members of X<c1> as this class is declared a
// friend.
template <class c1>
class friendlyGetThing {
public:
template <typename return_t>
static return_t getThing(X<c1> &xthis, const ::std::string ¶m,
return_t *);
};

// This can be partially specialized on either class, return type, or
// both, but it cannot be declared a friend, so will have no access to
// private or protected members of X<c1>.
template <class c1, typename return_t>
class getThingFunctor {
public:
typedef return_t r_t;

return_t operator()(X<c1> &xthis, const ::std::string ¶m) {
return_t *fred = 0;
return friendlyGetThing<c1>::getThing(xthis, param, fred);
}
};

template <class c1> class X {
public:
friend class friendlyGetThing<c1>;

template<typename returnT> returnT getAThing(std::string param) {
return getThingFunctor<c1, returnT>()(*this, param);
}
static std::string getName();
private:
c1 theData;
};

// This works ok...
template <class c1> std::string X<c1>::getName() {
return c1::getName();
}

class Y {
public:
static std::string getName() { return "Y"; }
};

template <class c1>
class getThingFunctor<c1, int> {
public:
int operator()(X<c1> &xthis, const ::std::string ¶m) {
return getIntThing(param);
}
};

// More specialized definitions of getAThingFunctor for other types/classes
// go here...

int main(int argc, char* argv[])
{
using ::std::cout;
X<Y> tester;
int anIntThing = tester.getAThing<int>(std::string("param"));
cout << "Name: " << tester.getName() << '\n';
cout << "An int thing: " << anIntThing << '\n';
}

I would recommend declaring getThingFunctor and friendlyGetThing in a semi-private utility namespace.

Is it safe to place definition of specialization of template member function (withOUT default body) in source file?

Lightness Races in Orbit cited why it's not compliant parts from the Standard. There might be some others, in the vicinity.

I will try to explain in simpler terms what the Standard verbiage means, and hopefully I'll get it correctly, and finally explain the linker errors (or absence of error):

  1. What is the point of instantiation ?
  2. How does the compiler select a specialization ?
  3. What is necessary at the point of instantiation ?
  4. Why a linker error ?

1/ What is the point of instantiation ?

The point of instantiation of a template function is the point where it is called or referred to (&std::sort<Iterator>) with all the template parameters fleshed out (*).

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

int main() { foo(1); } // point of instantiation of "foo<int>(int)"

It can be delayed though, and thus not match the exact call site, for templates called from other templates:

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

template <typename T>
void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract"

int main() { foo(1); } // point of instantiation of "bar<int>(int)"
// and ALSO of "foo<int>(int)"

This delay is very important as it enables writing:

  • co-recursive templates (ie, templates that refer to each-others)
  • user-specializations

(*) Roughly speaking, there are exceptions such as non-template methods of a template class...


2/ How does the compiler select a specialization ?

At the point of instantiation, a compiler need to be able to:

  • decide which base template function to call
  • and possibly, which of its specializations to call

This old GotW shows off the woes of specializations... but in short:

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

are overloads, and each spawns a distinct family of possible specializations of which they are the base.

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

is a specialization of 1, and

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

is a specialization of 2.

In order to resolve the function call, the compiler will first pick the best overload, while ignoring template specializations, and then, if it picked a template function, check if it has any specialization that could better apply.


3/ What is necessary at the point of instantiation ?

So, from the way a compiler resolve the call, we understand why the Standard specifies that any specialization should be declared before its first point of instantiation. Otherwise, it simply would not be considered.

Thus, at the point of instantiation, one needs to have already seen:

  • a declaration of the base template function to be used
  • a declaration of the specialization to be selected, if any

But what of the definition ?

It is not needed. The compiler assumes it will either be provided later on in the TU or by another TU entirely.

Note: it does burden the compiler because it means it needs to remember all the implicit instantiations it encountered and for which it could not emit a function-body so that when it finally encounters the definition it can (at last) emit all the necessary code fo all the specializations it encountered. I wonder why this particular approach was selected, and also wonder why even in the absence of an extern declaration the TU may end with undefined function-bodies.


4/ Why a linker error ?

Since no definition is provided, gcc trusts you to provide it later and simply emits a call to an unresolved symbol. If you happen to link with another TU that provides this symbol, then everything will be fine, and otherwise you'll get a linker error.

Since gcc follows the Itanium ABI we can simply look up how it mangles the symbols. It turns out that the ABI makes no difference in mangling specializations and implicit instantiations thus

cls.f( asd );

calls _ZN3cls1fIPKcEEvT_ (which demangles as void cls::f<char const*>(char const*)) and the specialization:

template<>
void cls::f( const char* )
{
}

also produces _ZN3cls1fIPKcEEvT_.

Note: it is not clear to me whether an explicit specialization could have been given a different mangling.



Related Topics



Leave a reply



Submit