函数模板部分专门化?

我知道下面的代码是一个类的部分专门化:

template <typename T1, typename T2>
class MyClass {
…
};




// partial specialization: both template parameters have same type
template <typename T>
class MyClass<T,T> {
…
};

我也知道 C + + 不允许函数模板部分专门化(只允许全部)。但是我的代码是否意味着我已经为一个/同一类型的参数部分地专门化了函数模板?因为它适用于 MicrosoftVisualStudio2010Express!如果没有,那么请您解释一下部分专业化的概念好吗?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;


template <typename T1, typename T2>
inline T1 max (T1 const& a, T2 const& b)
{
return a < b ? b : a;
}


template <typename T>
inline T const& max (T const& a, T const& b)
{
return 10;
}




int main ()
{
cout << max(4,4.2) << endl;
cout << max(5,5) << endl;
int z;
cin>>z;
}
124369 次浏览

No. For example, you can legally specialize std::swap, but you cannot legally define your own overload. That means that you cannot make std::swap work for your own custom class template.

Overloading and partial specialization can have the same effect in some cases, but far from all.

Function partial specialization is not allowed yet as per the standard. In the example, you are actually overloading & not specializing the max<T1,T2> function.
Its syntax should have looked somewhat like below, had it been allowed:

// Partial specialization is not allowed by the spec, though!
template <typename T>
inline T const& max<T,T> (T const& a, T const& b)
{            //    ^^^^^ <--- supposed specializing here as an example
return a; // can be anything of type T
}

In the case of a function templates, only full specialization is allowed by the C++ standard.
There are some compiler extensions which allows partial specialization, but the code looses its portability in such case!

What is specialization ?

If you really want to understand templates, you should take a look at functional languages. The world of templates in C++ is a purely functional sublanguage of its own.

In functional languages, selections are done using Pattern Matching:

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a


-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool


-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

As you can see, we overload the definition of isJust.

Well, C++ class templates work exactly the same way. You provide a main declaration, that states the number and nature of the parameters. It can be just a declaration, or also acts as a definition (your choice), and then you can (if you so wish) provide specializations of the pattern and associate to them a different (otherwise it would be silly) version of the class.

For template functions, specialization is somewhat more awkward: it conflicts somewhat with overload resolution. As such, it has been decided that a specialization would relate to a non-specialized version, and specializations would not be considered during overload resolution. Therefore, the algorithm for selecting the right function becomes:

  1. Perform overload resolution, among regular functions and non-specialized templates
  2. If a non-specialized template is selected, check if a specialization exist for it that would be a better match

(for on in-depth treatment, see GotW #49)

As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead.

Is this a template specialization ?

No, it is simply an overload, and this is fine. In fact, overloads usually work as we expect them to, while specializations can be surprising (remember the GotW article I linked).

Since partial specialization is not allowed -- as other answers pointed --, you could work around it using std::is_same and std::enable_if, as below:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
std::cout << ">>> messing with ints! " << f << std::endl;
}


template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
std::cout << ">>> messing with floats! " << f << std::endl;
}


int main(int argc, char *argv[]) {
typed_foo<int>("works");
typed_foo<float>(2);
}

Output:

$ ./a.out
>>> messing with ints! works
>>> messing with floats! 2

Edit: In case you need to be able to treat all the other cases left, you could add a definition which states that already treated cases should not match -- otherwise you'd fall into ambiguous definitions. The definition could be:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}


int main(int argc, char *argv[]) {
typed_foo<int>("works");
typed_foo<float>(2);
typed_foo<std::string>("either");
}

Which produces:

$ ./a.out
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Although this all-cases thing looks a bit boring, since you have to tell the compiler everything you've already done, it's quite doable to treat up to 5 or a few more specializations.

Late answer, but some late readers might find it useful: Sometimes, a helper function – designed such that it can be specialised – can solve the issue, too.

So let's imagine, this is what we tried to solve:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
R* r = new R(x);
f(r, y); // another template function?
}


// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y)
{
// unfortunately, Wrapper has no constructor accepting int:
Wrapper* w = new Wrapper();
w->setValue(x);
f(w, y);
}

OK, partial template function specialisation, we cannot do that... So let's "export" the part needed for specialisation into a helper function, specialize that one and use it:

template <typename R, typename T>
R* create(T t)
{
return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
Wrapper* w = new Wrapper();
w->setValue(n);
return w;
}


template <typename R, typename X, typename Y>
void function(X x, Y y)
{
R* r = create<R>(x);
f(r, y); // another template function?
}

This can be interesting especially if the alternatives (normal overloads instead of specialisations, the workaround proposed by Rubens, ... – not that these are bad or mine is better, just another one) would share quite a lot of common code.

Non-class, non-variable partial specialization is not allowed, but as said:

All problems in computer science can be solved by another level of indirection. —— David Wheeler

Adding a class to forward the function call can solve this, here is an example:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;


struct fun_tag {};


template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
std::forward<Ts>(ts)...);
}


template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
constexpr static R call(Ts&&... ts) { return {0}; }
};


template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
constexpr static R call(T, T) { return {1}; }
};


template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
constexpr static R call(int, int) { return {2}; }
};


template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
constexpr static R call(int, char) { return {3}; }
};


template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
constexpr static R call(char, T2) { return {4}; }
};


static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");


static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");


static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");


static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");


static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");


static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");


static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");


static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");


static_assert(
std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");


static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

I m sorry for the late answer, but i ve found a solution which i don't see explained (not directly at least) in the other answers.

A function cannot be partially specialized, while a class can. What can do the trick here is a static function inside class. We can make it works basically moving the "template partial specialization" inside a class specialization and creating inside it the function marked as static. This will allow us, by incrementing a bit the lines of code required, to construct our partially specialized function.

Let's consider the unavailable partially specialized function Printer as follow (code which doesn't compile at all).

template <class T, class Trait = void>
void Printer(const T&);


template <class T>
void Printer<T, std::enable_if_t<std::is_floating_point_v<T>>>(const T& v){
std::cout << "I m partially specialized for any floating point type." << std::endl;
}
template <class T>
void Printer<T, std::enable_if_t<std::is_integral_v<T>>>(const T& v){
std::cout << "I m partially specialized for any integral type." << std::endl;
}

We can make it works using static class functions and moving the partial specialization on class instead like this:

namespace detail{
    

template<class T, class Trait = void>
struct Specialized;


template<class T>
struct Specialized<T, std::enable_if_t<std::is_floating_point_v<T>>>
{
static void Printer(const T& v){
std::cout << "I m specialized for any floating point type"<< std::endl;
}
};


template<class T>
struct Specialized<T, std::enable_if_t<std::is_integral_v<T>>>
{
static void Printer(const T& v){
std::cout << "I m specialized for any integral type"<< std::endl;
}
};
}




template<class T>
void Printer(const T& v)
{
detail::Specialized<T>::Printer(v);
}

It results a bit longer but would solve our issue with a relatively clear way. You can test it on godbolt here.