bkxp
bkxp

Reputation: 1145

Template specialization restricted by a condition

Is it possible to make compiler choose between template specifications depending on type traits? For example, consider two template implementations of a Compare functional, one for sequential types (strings, vectors, lists, etc.) and another one for integer types. Can we have only one template specialization for the each class of types?

template <class SeqT>
class Compare
{
public:
   bool operator()(const SeqT& s1, const SeqT& s2) const
   {
      typename SeqT::const_iterator it1=s1.begin();
      typename SeqT::const_iterator it2=s2.begin();
      while(it1!=s1.end() && it2!=s2.end())
      {
         if(*it1<*it2) return true;
         if(*it2<*it1) return false;
         ++it1; ++it2;
      }
      return it2!=s2.end();
   }
};

template <class IntegerT>
class Compare
{
public:
   bool operator()(IntegerT i1, IntegerT i2) const
   {
      return i1<i2;
   }
};

template <class T, class Cmp = Compare<T> >
class SomeContainer
{
   ...
};

Basically, what I am looking for is a way to partially specialize a template by imposing a condition on the template argument. Like the first Compare<> specialization should be applied to the following types: std::basic_string<>, std::vector<>, std::list<>, and the second for the following types: int, unsigned, short, char. Is that possible?

Upvotes: 3

Views: 3388

Answers (1)

bolov
bolov

Reputation: 75697

This is my answer to another question, but it is what you need. It uses SFINAE to create template specialization ONLY for the template arguments that test true for a condition (like beeing a specific type).

https://stackoverflow.com/a/20898554/2805305

Edit

But how can I exactly specify that the first Compare specialization can be applied to e.g. std::basic_string and std::vector?

You create a trait that tells you if T is a vector or basic_string or list:

#include <iostream>
#include <vector>
#include <string>
#include <list>
#include <complex>
#include <type_traits>
using namespace std;

template <class T>
struct is_seq : std::false_type {
};

template <class T>
struct is_seq<std::vector<T>> : std::true_type {
};

template <class T>
struct is_seq<std::basic_string<T>> : std::true_type {
};

template <class T>
struct is_seq<std::list<T>> : std::true_type {
};

template <class T>
using enable_if_seq_type = typename std::enable_if<is_seq<T>::value>::type;

template <class T>
using enable_if_integral_type = typename std::enable_if<std::is_integral<T>::value>::type;


template <class T, class Enable = void>
class Compare; // <--------  define if you want a Compare for any type that doesn't match any specialization

template <class T>
class Compare<T, enable_if_seq_type<T>> { // specialization for T a vector, string or list
    public:
        void foo() {
            cout << "vector, string and list specialization" << endl;
        }
};

template <class T>
class Compare<T, enable_if_integral_type<T>> { // specialization for T an integral type
    public:
        void foo() {
            cout << "integral specialization" << endl;
        }
};


int main() {
    cout << std::boolalpha;

    cout << is_seq<int>::value << endl; // false
    cout << is_seq<std::vector<int>>::value << endl; // true

    Compare<int> c1; // uses second specialization
    c1.foo(); // output "integral specialization" 

    Compare<std::vector<int>> c2; // uses first specialization
    c2.foo(); // output "vector, string and list specialization"

    //Compare<std::complex<int>> c3;
    // compile error if you just declare and not define the generic Compare.
    // If you define the generic Compare, this will compile and it will use
    // that definition

    return 0;
}

http://ideone.com/JUbwla

If you want to be able to instantiate the class Compare for any other type, then you define the first (general) Compare declaration.

Upvotes: 3

Related Questions