Question

Let's say, I have six types, and they each belong in a conceptual category.
Here is a diagram that shows this:

Types A, B, and C wrapped inside a box called "Type Category 1" and types D, E, and F wrapped inside a box called "Type Category 2"


Or Perhaps a more specific example for you: Apple, Orange and Banana are all Fruit.  Carrot, Onion, and Cabbage are all Vegetables


I want to write two functions that will handle all 6 types.
Types in "Category 1" get handled a certain way, and types in "Category 2" get handled a different way.

Let's get into the code. First, I'll create the six types.

//Category 1 Types
class Type_A{};
class Type_B{};
class Type_C{};

//Category 2 Types
class Type_D{};
class Type_E{};
class Type_F{};

Next, I'll create two type traits so that the category of the type can be discovered at compile time.

/* Build The Category 1 Type Trait */

//Type_A Type Trait
template <typename T>
struct Is_Type_A {
  static const bool value = false;
};
template <>
struct Is_Type_A<Type_A> {
  static const bool value = true;
};

//Type_B Type Trait
template <typename T>
struct Is_Type_B {
  static const bool value = false;
};
template <>
struct Is_Type_B<Type_B> {
  static const bool value = true;
};

//Type_C Type Trait
template <typename T>
struct Is_Type_C {
  static const bool value = false;
};
template <>
struct Is_Type_C<Type_C> {
  static const bool value = true;
};

//Category 1 Type Trait
template <typename T>
struct Is_Type_From_Category_1 {
  static const bool value = Is_Type_A<T>::value || Is_Type_B<T>::value || Is_Type_C<T>::value;
};

/* Build The Category 2 Type Trait */

//Type_D Type Trait
template <typename T>
struct Is_Type_D {
  static const bool value = false;
};
template <>
struct Is_Type_D<Type_D> {
  static const bool value = true;
};

//Type_E Type Trait
template <typename T>
struct Is_Type_E {
  static const bool value = false;
};
template <>
struct Is_Type_E<Type_E> {
  static const bool value = true;
};

//Type_F Type Trait
template <typename T>
struct Is_Type_F {
  static const bool value = false;
};
template <>
struct Is_Type_F<Type_F> {
  static const bool value = true;
};

//Category 1 Type Trait
template <typename T>
struct Is_Type_From_Category_2 {
  static const bool value = Is_Type_D<T>::value || Is_Type_E<T>::value || Is_Type_F<T>::value;
};

Now that I have two type traits to distinguish what category each of the six types fall into, I want to write two functions. One function will accept everything from Category 1, and the other function will accept everything from Category 2. Is there a way to do this without creating some kind of dispatching function? Can I find a way to have only two functions; one for each category?


EDIT: I have tried to use enable_if like this, but such an attempt will result in a compiler error.

//Handle all types from Category 1
template<class T ,class = typename std::enable_if<Is_Type_From_Category_1<T>::value>::type >
void function(T t){
    //do category 1 stuff to the type
    return;
}

//Handle all types from Category 2
template<class T ,class = typename std::enable_if<Is_Type_From_Category_2<T>::value>::type >
void function(T t){
    //do category 2 stuff to the type
    return;
}

Edit 2: I've tried the code provided in the link, but this isn't a yes or no decision on whether or not to call the function. It's which function do I call, given two type traits. This would be a redefinition error.

//Handle all types from Category 2
template<class T, class dummy = typename std::enable_if< Is_Type_From_Category_1<T>::value, void>::type>
void function(T t){
    //do category 1 stuff to the type
    return;
}
//Handle all types from Category 2
template<class T, class dummy = typename std::enable_if< Is_Type_From_Category_2<T>::value, void>::type>
void function(T t){
    //do category 2 stuff to the type
    return;
}
Was it helpful?

Solution

Two functions signatures are not allowed to differ only by the default value of a template parameter. What would happen if you explicitly called function< int, void >?

Usual usage of enable_if is as the function return type.

//Handle all types from Category 1
template<class T >
typename std::enable_if<Is_Type_From_Category_1<T>::value>::type
function(T t){
    //do category 1 stuff to the type
    return;
}

//Handle all types from Category 2
template<class T >
typename std::enable_if<Is_Type_From_Category_2<T>::value>::type
function(T t){
    //do category 2 stuff to the type
    return;
}

OTHER TIPS

I think using tag despatch would be easier than SFINAE.

template<class T>
struct Category;

template<>
struct Category<Type_A> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_B> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_C> : std::integral_constant<int, 1> {};

template<>
struct Category<Type_D> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_E> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_F> : std::integral_constant<int, 2> {};

template<class T>
void foo(std::integral_constant<int, 1>, T x)
{
    // Category 1 types.
}

template<class T>
void foo(std::integral_constant<int, 2>, T x)
{
    // Category 2 types.
}

template<class T>
void foo(T x)
{
    foo(Category<T>(), x);
}

As an alternative to category selection via "traits", you can also consider CRTP (where the type carries the category as a base):

template<class Derived> class category1 {};
template<class Derived> class category2 {};

class A1: public category1<A1> { ..... };
class A2: public category2<A2> { ..... };
class B1: public category1<B1> { ..... };
class B2: public category2<B2> { ..... };

template<class T>void funcion_on1(category1<T>& st)
{
   T& t = static_cast<T&>(st);
   .....
}

template<class T>void funcion_on1(category2<T>& st)
{
   T& t = static_cast<T&>(st);
   .....
}

The advantage is to have a less polluted namespace.

I learned the following technique from R. Martinho Fernandes. The code shown below is written to illustrate the bare bones of the problem but you should refer to this blog post to get the full range of tricks to make it pretty.

You've already mentioned that you're running into problems because of signatures being identical. The trick is to make the types to be different.

Your second approach is close, but we can't use void as the resulting type of the std::enable_if<>.

Note that the following code does not compile, and specifying void for the std::enable_if<> does not change anything since the default is void anyway.

#include <iostream>

class A {};
class B {};

template <
    typename T, 
    typename = typename std::enable_if<std::is_same<T, A>::value>::type>
void F(T) {
  std::cout << "A" << std::endl;
}

template <
    typename T, 
    typename = typename std::enable_if<std::is_same<T, B>::value>::type>
void F(T) {
  std::cout << "B" << std::endl;
}

int main() {
  F(A{});
  F(B{});
}

The reason, as you already described is because the signatures are identical. Let's differentiate them.

#include <iostream>

class A {};
class B {};

template <
    typename T,
    typename std::enable_if<std::is_same<T, A>::value, int>::type = 0>
void F(T) {
  std::cout << "A" << std::endl;
}

template <
    typename T, 
    typename std::enable_if<std::is_same<T, B>::value, int>::type = 0>
void F(T) {
  std::cout << "B" << std::endl;
}

int main() {
  F(A{});
  F(B{});
}

Prints:

A
B

We have now differentiated the types between the 2 functions because rather than the second template parameter being a type, it is now an int.

This approach is preferable to using std::enable_if<> in the return type for example since constructors don't have return types, the pattern wouldn't be applicable for those.

Notes: std::is_same<> is used with a single class to simplify the condition.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top