Question

I'm currently writing a template that operates differently based on the category of the input.

There are 3 cases I'm looking to add to my traits class.

A. The type has a typedef type_category, use that.

B. The type doesn't have the typedef, use the type regular_tag (most common case)

C. My specializations, std::list<T> uses the type special_tag for any T.

How would I manage this? It's simple to do either A. and C. or B. and C. but I'm not sure how to get all 3.

EDIT

A example might make it easier to understand.

class Foo
{
    typedef foo_tag type_category;
}
class Bar;

my_traits<Foo>::type(); // makes a foo_tag
my_traits<Bar>::type(); // makes a regular_tag
my_traits<std::list<Baz>>::type(); // makes a special_tag because I want special list proce

ssing.

Was it helpful?

Solution

The scaffolding could look like this:

template <typename T>
struct MyTrait
{
  typedef typename MyHelper<T, CheckForType<T>::value>::type tag;
};

template <typename T>
struct MyTrait<std::list<T>>
{
  typedef special_tag tag;
};

We need the helper:

template <typename T, bool>
struct MyHelper
{
  typedef regular_tag tag;
};
template <typename T>
struct MyHelper<T, true>
{
  typedef typename T::type_category tag;
};

Now all we need is a type-trait to check for a member typedef:

template<typename T>
struct CheckForType
{
private:
    typedef char                      yes;
    typedef struct { char array[2]; } no;

    template<typename C> static yes test(typename C::type_category*);
    template<typename C> static no  test(...);
public:
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

Usage:

MyTrait<Foo>::tag

OTHER TIPS

template<class T> 
T::type_category getType(T t, typename T::type_category c=typename T::type_category()) 
{ return c;}
template<class T> 
regular_tag getType(T t, ...) 
{ return regular_tag();}
template<class T>
special_tag getType(std::list<T> t, typename std::list<T>::type_category c=typename std::list<T>::type_category()) 
{ return special_tag();}

int main() {
    auto a = getType(int);
    auto b = getType(std::iterator_traits<char*>);
    auto c = getType(std::list<char>);
}

I'm not that great with SFINAE, so I doubt this even compiles, but something like this is the direction to look.

The code by Kerrek works, but the following code is shorter and more versatile:

template <typename T,typename DefaultType>
class MyTrait
{
    template <typename C> static DefaultType               test( ... );
    template <typename C> static typename C::type_category test( typename C::type_category * );
public:
    using type = decltype( test<T>(nullptr) );
};

The reason I say that Kerrek's code is not versatile is that it requires you to hard-code the type "special_tag" so your class MyTrait will always use the same default tag. The code that I provide would allow you to use the class MyTrait with different defaults. For instance, it might happen that somewhere in the code, you want the default to be a int if the type_category is not defined, but elsewhere, you want it to be a float.

Let's look at an example. Suppose we design a class which is meant to take a container class such as the standard library vector as a template parameter. In our class, we want to use the same size_type as the underlying container. However, it might happen that somebody gives us a container that does not have size_type defined (for instance, a valarray does not define size_type). In this case, let's pretend I want to use int as the default (you should probably use size_t like the other standard containers, but if I changed the code you wouldn't be able to tell that the code is actually working). For this scenario I changed the classname from "MyTrait" to "size_typeof", and I changed the "type_category" to "size_type" (since this is the thing I want to look for). Some code for this scenario along with an example main function to see the types of the determined variables are given below:

#include <iostream>
#include <vector>
#include <valarray>
#include <typeinfo>

template <typename T,typename DefaultType>
class size_typeof
{
    template <typename C> static DefaultType           test( ... );
    template <typename C> static typename C::size_type test( typename C::size_type * );
public:
    using type = decltype( test<T>(nullptr) );
};

template <typename ContainerType>
class Matrix {
    private:
        // Change int to size_t in real code. 
        using size_type = typename size_typeof<ContainerType,int>::type;
        size_type Rows;
        size_type Cols;
    public:
        Matrix( size_t rows, size_t cols ) : Rows(rows), Cols(cols) {}
        size_type rows(){ return Rows; }
        size_type cols(){ return Cols; }
};

int main()
{
    // Give the matrices some nonzero dimensions
    Matrix<std::vector<double>> vec(5,2);
    Matrix<std::valarray<double>> val(4,3);

    // vectors have a size_type, almost always defined as size_t. 
    // So this should return your compiler's code for size_t 
    // (on my computer, this is "m")
    std::cout << typeid( vec.rows() ).name() << std::endl; 

    // valarrays do not have a size_type. 
    // So this should return your compiler's code for int 
    // (on my computer, this is "i")
    // It should return this code, since we used int as the default  
    // size_type in the Matrix class
    std::cout << typeid( val.rows() ).name() << std::endl; 
    return 0;
}

Ok. That's great. But suppose I really did want to use just a hard-coded default for the DefaultType, instead of having to specify the DefaultType as a template parameter. Guess what? Template parameter defaults. Just define size_typeof like:

template <typename T,typename DefaultType = int>
class size_typeof {
    ...
};

and you are good to go. No need to specify the default type "int" anymore. For instance, in our Matrix class, we could have used

using size_type = size_typeof<ContainerType>;

The end.

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