3

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?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
bkxp
  • 1,115
  • 1
  • 12
  • 20

1 Answers1

3

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.

Community
  • 1
  • 1
bolov
  • 72,283
  • 15
  • 145
  • 224
  • Thank you. But how can I exactly specify that the first Compare specialization can be applied to e.g. std::basic_string and std::vector? Also, I don 't need compile to _fail_, but the compiler to _choose_ the appropriate template when the type argument matches some condition. So, like in the above example, if SomeContainer is instantiated, then Compare is used, and if SomeContainer is instantiated, then Compare is used. – bkxp Jan 12 '14 at 16:31
  • 1
    @bolov The above approach has an issue. The simple issue is that `std::vector` with unusual allocator, `std::string` with non-default char traits, etc all do not work. The underlying issue is that maintaining a list of `template` or types that match a condition is often a bad idea, compared to doing some kind of duck-type testing. – Yakk - Adam Nevraumont Jan 12 '14 at 17:15
  • @bolov Thanks for the code example! I can understand the concept of type enablers much better now. – bkxp Jan 13 '14 at 09:27
  • @Yakk You made a very important point about the default types in template declarations. It is probably not generally acceptable to use type-constrained template specializations. – bkxp Jan 13 '14 at 09:28