I have written the following code to assist restricting my template functions to certain types, with meaningful error message when other types were used. I had the idea from a different question in stackoverflow, on which I still can't comment because I am new here.
The macro compiles flawlessly under linux GCC but not with Visual Studio 2012.
#include <string>
#include <iostream>
#include <vector>
#include <cassert>
#include <type_traits>
#define ISALLOWED(DerivedT) (std::is_same<T, DerivedT>::value)||(std::is_base_of<T,DerivedT>::value)
#define FE_1(WHAT, X) WHAT(X)
#define FE_2(WHAT, X, ...) WHAT(X) || FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT(X) || FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT(X) || FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT(X) || FE_4(WHAT, __VA_ARGS__)
#define FE_6(WHAT, X, ...) WHAT(X) || FE_5(WHAT, __VA_ARGS__)
//... repeat as needed
#define GET_MACRO(_1,_2,_3,_4,_5,_6,NAME,...) NAME
#define FOR_EACH(action,...) \
GET_MACRO(__VA_ARGS__,FE_6,FE_5,FE_4,FE_3,FE_2,FE_1)(action,__VA_ARGS__)
// this is where you need to add types
#define ASSERTIOTYPES \
static_assert(FOR_EACH(ISALLOWED,\
int,double,std::string\
),"Type not defined for this template.");
template<class T> std::ostream & operator<<(std::ostream &os,
const std::vector<T> & v) {
ASSERTIOTYPES;
os << "[";
for (size_t i = 0; i < v.size(); i++) {
os << v[i];
if (i != v.size() - 1)
os << ", ";
}
os << "]";
return os;
}
The error message is: error C2977: 'std::is_same' : too many template arguments
And it appears only when I define ASSERTIOTYPES with more than one type, but when it is defined with only one type, for example:
#define ASSERTIOTYPES \
static_assert(FOR_EACH(ISALLOWED,\
int\
),"Type not defined for this template.");
... the code compiles normally.
Any idea how to solve this?
template<typename T> struct is_io_type : std::false_type {};
template<> struct is_io_type<int> : std::true_type {};
template<> struct is_io_type<double> : std::true_type {};
template<> struct is_io_type<std::string> : std::true_type {};
Then, instead of your macro, simply type:
static_assert( is_io_type<T>::value, "Type not defined for this template." )
which is about as verbose and not nearly as obscure or hard to figure out what the heck is going on.
Now, suppose we really need the is_base_of
functionality.
template<typename T,typename=void> struct is_io_type : std::false_type {};
template<> struct is_io_type<int,void> : std::true_type {};
template<typename T> struct is_io_type<T,typename std::enable_if<std::is_base_of<int, T>::type> : std::true_type {};
template<> struct is_io_type<double,void> : std::true_type {};
template<typename T> struct is_io_type<T,typename std::enable_if<std::is_base_of<double, T>::type> : std::true_type {};
template<> struct is_io_type<std::string,void> : std::true_type {};
template<typename T> struct is_io_type<T,typename std::enable_if<std::is_base_of<std::string, T>::type> : std::true_type {};
which gets pretty verbose.
But rather than creating a variardic macro expansion at the point where we use the macro, we can instead use a macro to build the above type trait specializations.
So you first create your type trait, then extend it:
CREATE_TYPE_CATEGORY( is_io_type );
ADD_BASE_TYPE_CATEGORY( is_io_type, int );
ADD_BASE_TYPE_CATEGORY( is_io_type, double );
ADD_BASE_TYPE_CATEGORY( is_io_type, std::string );
which would write the code above a bit less verbosely. It also supports distributed trait modification: if someone creates a new type that should be is_io_type
, they can use the above macro after introducing the type and it (and its descendents) become an io type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With