partial specialization of function templates

7.5k views Asked by At

In the below code snippet,

template<typename T1>
void func(T1& t)
{
    cout << "all" << endl;
}

template<typename T2>
void func(T2 &t)
{
    cout << "float" << endl;
}

// I do not want this
// template<> void func(float &t)

int main()
{
    int i; float f;
    func(i); // should print "all"
    func(f); // should print "float" 
    return 0;
}

I would like to have the templates modified which by passing any type other than float will print "all" and passing float will print "float". I do not want template specialization, instead have partial specialization which will act accordingly based on input type. How should i go about it. Thanks in advance.

Well the scenario, i'm currently facing is like, I need to have the following defined,

template<typename T1>
void func(T1 &t)
{
    cout << "t1" << endl;
}

template<typename T2>
void func(T2 &t)
{
    cout << "t2" << endl;
}

The following calls should print "t2"

func(int) // print "t2"
func(float) // print "t2"
func(string) // print "t2"

The following calls should print "t1"

func(char) // print "t1"
func(xyz) // print "t1"
...
func(abc) // print "t1"

some kind of grouping like the above where few should call the partial specialization implementation and others should call the default implementation.

5

There are 5 answers

2
Fred Nurk On BEST ANSWER

Write a type traits class for your condition:

template<class T>
struct IsIntFloatOrString {
  enum { value = boost::is_same<T, int>::value
              or boost::is_same<T, float>::value
              or boost::is_same<T, string>::value };
};

Use boost::enable_if and disable_if:

template<typename T1>
typename boost::enable_if<IsIntFloatOrString<T1> >::type
func(T1 &t) {
  cout << "t1" << endl;
}

template<typename T2>
typename boost::disable_if<IsIntFloatOrString<T2> >::type
func(T2 &t) {
  cout << "t2" << endl;
}
0
Lightness Races in Orbit On

You cannot partially specialise functions in C++.

Perhaps this is not the terminology you mean. You can use templates like boost::is_same<T1, T2> to perform conditional logic based on the given template parameter. You can also use T in any place where you'd use any other type, such as in typeid(T).name():

template <typename T>
void foo(T&) {
   if (boost::is_same<T, int>::value)
      std::cout << "int lol";
   else
      std::cout << typeid(T).name();
}

(Although I'd not recommend using typeid().name() as its value is not specified by the standard and can vary from the type written in your code, to a mangled symbol, or the lyrics to Pokerface.)

Addendum Like other answerers, I would personally choose template specialisation itself or just plain ol' function overloading. I don't know why you're averse to them, but that is what they are there for.

0
Allison Lock On

You can combine function overloading with templates. So:

#include <iostream>

template<typename T>
void func(T& t)
{
    std::cout << "all" << std::endl;
}

void func(float& f)
{
    std::cout << "float" << std::endl;
}

int main()
{
    int i; float f;
    func(i); // prints "all"
    func(f); // prints "float" 
    return 0;
}
0
BЈовић On

As Tomalak already said in his answer you can not partially specialize a template function, but if you change your function to be a static member function in a template class, you could do it.

However, a better approach would be function overloading.

0
RiaD On

This is how to make it work without ugly syntax a and !b and !c for enable_if in case of arbitrary number of conditions.

If we know that partial specialization don't work work function but work with classes, let's use classes! We should hide it from people, but we can use them!

OK, code:

#include <type_traits>
#include <iostream>


template <typename T>
class is_int_or_float : public std::integral_constant<bool, std::is_same<T, int>::value || std::is_same<T, float>::value> {
};


template<typename T, typename Enable = void> //(2)
struct Helper {
    static void go(const T&) {
                std::cout << "all"<< std::endl;
        }
};

template<typename T>
struct Helper<T, typename std::enable_if<is_int_or_float<T>::value>::type> { // (3)
        static void go(const T&) {
                std::cout << "int or float" << std::endl;
        }
};

template<typename T>
struct Helper<T, typename std::enable_if<std::is_pointer<T>::value>::type> { // (3)
        static void go(const T&) {
                std::cout << "pointer" << std::endl;
        }
};

template<typename T>
void func(const T& arg) {
        Helper<T>::go(arg); // (1)
}
int main() {
        char c;
        int i;
        float f; 
        int* p;
        func(c);
        func(i);
        func(f);
        func(p);
}

(1) First of all just for every type call helper. No specialization for functions.
(2) Here we add one dummy argument. We don't have to specify it on calling because it's default to void (3) In 3 we just give void, when we allow T and anything else (or SFINAE as in our case). One important thing is that we shouldn't allow some T twice or more.

Notes:

  1. We can also change default type to std::true_type, after that we will be able to get rid of std::enable_if (std::enable_if<some_trait<T>::value> will be change to just some_trait<T>::type). I'm not sure which

  2. This code uses type traits from C++11. If you don't have c++11 support you may write your own traits or use type traits from boost

Live example