123

I know that the below code is a partial specialization of a class:

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


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

Also I know that C++ does not allow function template partial specialization (only full is allowed). But does my code mean that I have partially specialized my function template for one/same type arguments? Because it works for Microsoft Visual Studio 2010 Express! If no, then could you please explain the partial specialization concept?

#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;
}
3
  • Look for that analogy of class specialization. If it is called class specialization, then why I should consider the same thing for function as overloading??
    – Narek
    Commented Nov 9, 2011 at 7:15
  • 1
    No, specialization syntax is different. Look at the (supposed) function specialization syntax in my answer below.
    – iammilind
    Commented Nov 9, 2011 at 7:19
  • 2
    Why doesn't this throw a "Call to max is ambigious" error? How does max(5,5) resolve to max(T const&, T const&) [with T=int] and not max(T1 const&, T2 const&) [with T1=int and T2=int]?
    – NHDaly
    Commented Aug 13, 2015 at 5:34

7 Answers 7

110

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!

11
  • 1
    @Narek, Partial function specialization is not part of standard (for whatsoever reasons). I think MSVC supports it as an extension. May be after sometime, it would be allowed by other compilers also.
    – iammilind
    Commented Nov 9, 2011 at 7:29
  • 1
    @iammilind: No problem. He already seems to know that. That is why he is trying that for function template as well. So I edited it again, making it clear now. Commented Nov 9, 2011 at 8:43
  • 51
    Anyone who can explain why partial specialization isn't allowed? Commented Sep 18, 2015 at 21:37
  • 3
    @NHDaly, It doesn't give ambiguity error because 1 function is better match than the other. Why it selects (T, T) over (T1, T2) for (int, int), is because the former guarantees that there are 2 parameters and both types are same; the latter only guarantees that there are 2 parameters. Compiler chooses always an accurate description. e.g. If you have to make a choice between 2 descriptions of a "River" which one would you choose? "collection of water" vs "collection of Water flowing".
    – iammilind
    Commented Oct 23, 2015 at 5:54
  • 2
    @kfsone, I think this feature is under review, hence open for interpretation. You may refer this open-std section, which I saw in Why does the C++ standard not allow function template partial specialization?
    – iammilind
    Commented Dec 22, 2018 at 2:21
58

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.

4
  • There really isn't any need to do this as this can be handled by function overloading in a much simpler and clearer fashion.
    – Adrian
    Commented Jan 5, 2015 at 4:45
  • 3
    @Adrian I really can't think of any other function overloading approach to solve this. You noticed partial overloading is not allowed, right? Share with us your solution, if you think it is clearer.
    – Rubens
    Commented Jan 9, 2015 at 10:24
  • 1
    is there any other way to do easily catch all templated function?
    – Nick
    Commented Sep 6, 2016 at 12:55
  • 1
    @Adrian It is true one may in some cases redefine typed_foo in such a way that it only takes one template argument instead of two, and then use overloading like you said. However, this is not really what the OP was asking. And besides, I'm not sure if you could do a catch-all function purely with overloads. Also, you might want your catch-all implementation to cause a compile error when it is used, which is only possible with template functions, where a template-dependent line would cause the compiler to issue an error.
    – adentinger
    Commented Jul 23, 2021 at 15:42
19

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).

2
  • "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." How about with non type template parameters? Commented Jul 24, 2013 at 22:50
  • @Julius: you can still use overloading, albeit by introducing a dummy parameter such as boost::mpl::integral_c<unsigned, 3u>. Another solution could also be to use enable_if/disable_if, though it's a different story. Commented Jul 25, 2013 at 8:51
11

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, "");
1
  • I don't want to hear my crazy idea does not work, or that the standard some crap. I want to hear that with enough effort it will work.
    – dEmigOd
    Commented Oct 24, 2022 at 11:31
6

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.

------ EDIT : Thanks to KROy for the hint

It can be made even shorter by just wrapping the two static functions inside a struct leaving the template specialization on them:

namespace detail{
    struct Specialized{
        template<class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
        static void Printer(const T& v){
            std::cout << "I'm specialized for integral types." << std::endl;
        }

        template<class T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
        static void Printer(const T& v){
            std::cout << "I'm specialized for floating point types." << std::endl;
        }
    };
}

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

It can be tested on godbolt here.

2
  • Just wrap the function around a struct and it will work. Check how std::Optional calls the dtor.
    – KRoy
    Commented Mar 21, 2023 at 22:09
  • @KRoy Thank you for your tip, i've adjusted the answer Commented Mar 23, 2023 at 14:19
4

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.

1
  • 4
    That's why you put your swap overload in your namespace.
    – jpalecek
    Commented Nov 9, 2011 at 9:41
2

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.