C++: Using inner typedef if available in traits class or default
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 typespecial_tag
for anyT
.
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_trait开发者_如何学Gos<Bar>::type(); // makes a regular_tag
my_traits<std::list<Baz>>::type(); // makes a special_tag because I want special list proce
ssing.
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
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.
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.
精彩评论