11

Is there a technique / best style to group class template specializations for certain types ?

An example : Lets say I have a class template Foo and I need to have it specialized the same for the typeset

A = { Line, Ray }

and in another way for the typeset B

B = { Linestring, Curve }

What I'm doing so far : (the technique is also presented here for functions)

#include <iostream>
#include <type_traits>
using namespace std;

// 1st group
struct Line    {};
struct Ray     {};
// 2nd group 
struct Curve      {};
struct Linestring {};

template<typename T, typename Groupper=void>
struct Foo
{ enum { val = 0 }; };

// specialization for the 1st group 
template<typename T>
struct Foo<T, typename enable_if<
    is_same<T, Line>::value ||
    is_same<T, Ray>::value
>::type>
{
    enum { val = 1 };
};

// specialization for the 2nd group 
template<typename T>
struct Foo<T, typename enable_if<
    is_same<T, Curve>::value ||
    is_same<T, Linestring>::value
>::type>
{
    enum { val = 2 };
};

int main() 
{
    cout << Foo<Line>::val << endl;
    cout << Foo<Curve>::val << endl;
    return 0;
}

An extra helper struct enable_for would shorten the code (and allow to write the accepted types directly). Any other suggestions, corrections? Shouldn't this involve less effort?

Community
  • 1
  • 1
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153

2 Answers2

13

You can also do this with your own traits and without enable_if:

// Traits

template <class T>
struct group_number : std::integral_constant<int, 0> {};

template <>
struct group_number<Line> : std::integral_constant<int, 1> {};

template <>
struct group_number<Ray> : std::integral_constant<int, 1> {};

template <>
struct group_number<Linestring> : std::integral_constant<int, 2> {};

template <>
struct group_number<Curve> : std::integral_constant<int, 2> {};


// Foo

template <class T, int Group = group_number<T>::value>
class Foo
{
  //::: whatever
};

template <class T>
class Foo<T, 1>
{
  //::: whatever for group 1
};

template <class T>
class Foo<T, 2>
{
  //::: whatever for group 2
};

This has the advantage of automatically ensuring that each type is in at most one group.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
11

Extra level of indirection by using two new type traits:

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

template<>
struct is_from_group1<Line>: std::true_type {};

template<>
struct is_from_group1<Ray>: std::true_type {};

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

template<>
struct is_from_group2<Curve>: std::true_type {};

template<>
struct is_from_group2<Linestring>: std::true_type {};

and then do the enable_if on these type traits

// specialization for the 1st group 
template<typename T>
struct Foo<T, typename enable_if<
    is_from_group1<T>::value
>::type>
{
    enum { val = 1 };
};

// specialization for the 2nd group 
template<typename T>
struct Foo<T, typename enable_if<
    is_from_group2<T>::value
>::type>
{
    enum { val = 2 };
};

Note that you still need to make sure that no user-defined class is added to both groups, or you will get an ambiguity. You can either use @Angew's solution to derive from a numbered group using std::integral_constant<int, N> for group number N. Or, if these groups are not logically exclusive, you could add an extra condition inside the enable_if that guards against this.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304