About variadic templates
I'm currently experiencing with the new c++0x variadic templates, and it's quite fun, Although I have a question about the process of member instantiation.
in this example, I'm trying to emulate the strongly typed enum with the possibility of choose a random valid strong enum (this is used for unit testing).
#include<vector>
#include<iostream>
using namespace std;
template<unsigned... values> struct sp_enum;
/*
this is the solution I found, declaring a globar var
vector<unsigned> _data;
and it work just fine
*/
template<> struct sp_enum<>{
static const unsigned _count = 0;
static vector<unsigned> _data;
};
vector<unsigned> sp_enum<>::_data;
template<unsigned T, unsigned... values>
struct sp_enum<T, values...> : private sp_enum<values...>{
static const unsigned _count = sp_enum<values...>::_count+1;
static vector<unsigned> _data;
sp_enum( ) : sp_enum<values...>(values...) {_data.push_back(T);}
sp_enum(unsigned v ) {_data.push_back(v);}
sp_enum(unsigned v, unsigned...) : sp_enum<values...>(values...) {_data.push_back(v);}
};
template<unsigned T, unsigned... values> vector<unsigned> sp_enum<T, values...>::_data;
int main(){
enum class t:unsigned{Default = 5, t1, t2};
sp_enum<t::Default, t::t1, t::t2> test;
cout <<test._count << endl << test._data.size() << endl;
for(auto i= test._data.rbegin();i != test._data.rend();++i){cout<< *i<< ":";}
}
the result I'm getting with this code is :
3 1 5:
can someone point me what I'm messing here ???
Ps: using gcc 4.4.3
I have reworked the code to be more generic and to reduce the hardcore coding as possible (@Matthieu M.). But I would like to explain why I'm doing all this before.
I have, as many developer embraced the new c++0x standard in my code and I'm quit happy about it. But I have an issue with the strongly typed enums when trying to write test units.
The problem is that you cannot generate a random strong typed enum (I know, that you can, but wanted to do it in a more elegant way). So, now with this code, I can, using variadic templates and variadic macro (yes old dirty macro) declare and randomly select a stron开发者_运维知识库gly typed and scoped enum.
here is the code:
#include<vector>
#include<iostream>
#include <boost/preprocessor/array/elem.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>
using namespace std;
template<typename T, T... values> class sp_enum;
template<typename T> class sp_enum<T>{
protected: static const unsigned _count = 0;
};
template<typename T, T head, T... values>
class sp_enum<T, head, values...> : public sp_enum<T, values...>{
protected:
static const unsigned _count = sp_enum<T, values...>::_count+1;
static vector<T> _data;
public:
sp_enum( ) : sp_enum<T, values...>(values...) {_data.push_back(head);for(auto i= sp_enum<T, values...>::_data.begin();i != sp_enum<T, values...>::_data.end();++i){_data.push_back(*i);}}
sp_enum(T v ) {_data.push_back(v );}
sp_enum(T v, T...) : sp_enum<T, values...>(values...) {_data.push_back(v );for(auto i= sp_enum<T, values...>::_data.begin();i != sp_enum<T, values...>::_data.end();++i){_data.push_back(*i);}}
vector<T> data() const { return _data ;}
unsigned count() const { return _count ;}
static T randomEnum() { srand (time(NULL));return _data[rand()%_count];}
};
template<typename T, T head, T... values> vector<T> sp_enum<T, head, values...>::_data;
#define PP_NARG(...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define PP_RSEQ_N() \
63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16, \
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0
#define FOREACH_ARRAY( ... ) (PP_NARG(__VA_ARGS__) , ( __VA_ARGS__ ) )
#define FOREACH( Name, A, ... ) BOOST_PP_REPEAT_FROM_TO(1, PP_NARG(Name, __VA_ARGS__), A, FOREACH_ARRAY(Name, __VA_ARGS__) )
#define DECORATION(z,n,data) ,BOOST_PP_ARRAY_ELEM( 0, data ) :: BOOST_PP_ARRAY_ELEM( n, data )
#define SP_ENUM(enumName, ...) \
enum class _##enumName : unsigned { Default, __VA_ARGS__ }; \
typedef sp_enum<_##enumName FOREACH( _##enumName, DECORATION, Default, __VA_ARGS__) > enumName;
SP_ENUM( xx, test1, test2, test3 )
int main(){
xx test;
cout <<test.count() << endl << test.data().size() << endl;
auto dt = test.data();
for(auto i = dt.rbegin(); i != dt.rend();++i){ cout<< (unsigned)*i << ":" ; }
cout << "random strongly typed enum : " << (unsigned) test.randomEnum() << endl;
}
What bother me now, is the limitation of the PP_NARG macro (I haven't found any other way to count the number of argument).
I will gladly accept any pointer or hint to enhance this.
static vector<unsigned> _data;
is not shared across sp_enum instantiations, only across instances of the template class with the same parameters.
Since this question has turned into a mess.
Are you after something like this? Sorry I don't know boost's preprocessor library, but it doesn't seem to be particularly necessary for that. The only question is, does count have to be a compile-time constant?
#include<vector>
#include<iostream>
#include <cstdlib>
#define DECLARE_ENUM(enum_name, ...) \
enum class enum_name##_ : unsigned { __VA_ARGS__}; \
class enum_name { \
public: \
static enum_name##_ random() { return static_cast<enum_name##_>(values[rand() % values.size()]); } \
static unsigned count() { return values.size(); } \
static std::vector<unsigned> data() { return values; } \
private: \
enum : unsigned {__VA_ARGS__}; \
static std::vector<unsigned> values; \
}; \
std::vector<unsigned> enum_name::values{__VA_ARGS__};
DECLARE_ENUM( xx, test1, test2, test3 )
int main(){
xx test;
std::cout <<test.count() << std::endl << test.data().size() << std::endl;
auto dt = test.data();
for(auto i = dt.rbegin(); i != dt.rend();++i){ std::cout<< (unsigned)*i << ":" ; }
xx_ random_value = test.random();
std::cout << "random strongly typed enum : " << (unsigned) random_value << std::endl;
}
Admittedly the design could be better, and I haven't bothered about the Default idea (it breaks down anyway, if you don't want consecutive values).
Another thing, if this only supports consecutive values, there is no reason for the vector in the first place. Just store the first (if not always 0) and the last value, and everything else can be computed. The data method might return a range of boost's counting_iterator
.
Or just specialize a corresponding traits class:
#include<iostream>
#include <cstdlib>
namespace detail {
template <unsigned ...values>
struct last;
template <unsigned N, unsigned ...values>
struct last<N, values...>
{
static const unsigned value = last<values...>::value;
};
template <unsigned N>
struct last<N>
{
static const unsigned value = N;
};
template <unsigned N, unsigned ...>
struct first
{
static const unsigned value = N;
};
}
template <class T>
struct enum_traits;
#define DECLARE_ENUM(enum_name, ...) \
enum class enum_name : unsigned { __VA_ARGS__}; \
template <> struct enum_traits<enum_name> { \
private: enum : unsigned { __VA_ARGS__ }; \
public: \
static const unsigned first = detail::first< __VA_ARGS__>::value; \
static const unsigned last = detail::last< __VA_ARGS__>::value; \
static const unsigned count = last - first + 1; \
};
template <class T>
T random_enum_value()
{
return static_cast<T>(rand() % enum_traits<T>::count + enum_traits<T>::first);
}
DECLARE_ENUM( xx, test1, test2, test3 )
int main(){
std::cout << enum_traits<xx>::first << ' ' << enum_traits<xx>::last << ' ' << enum_traits<xx>::count << '\n';
std::cout << (unsigned) random_enum_value<xx>() << '\n';
}
Thanks to Pavel, I figured out the problem.
here is the solution
#include<vector>
#include<iostream>
using namespace std;
template<unsigned... values> struct sp_enum;
template<> struct sp_enum<>{
static const unsigned _count = 0;
static vector<unsigned> _data;
};
vector<unsigned> sp_enum<>::_data;
template<unsigned T, unsigned... values>
struct sp_enum<T, values...> : private sp_enum<values...>{
static const unsigned _count = sp_enum<values...>::_count+1;
static vector<unsigned> _data;
sp_enum( ) : sp_enum<values...>(values...) {_data.push_back(T);for(auto i= sp_enum<values...>::_data.begin();i != sp_enum<values...>::_data.end();++i){_data.push_back(*i);}}
sp_enum(unsigned v ) {_data.push_back(v);}
sp_enum(unsigned v, unsigned...) : sp_enum<values...>(values...) {_data.push_back(v);for(auto i= sp_enum<values...>::_data.begin();i != sp_enum<values...>::_data.end();++i){_data.push_back(*i);}}
};
template<unsigned T, unsigned... values> vector<unsigned> sp_enum<T, values...>::_data;
int main(){
enum class t:unsigned{Default = 5, t1, t2};
sp_enum<t::Default, t::t1, t::t2> test;
cout <<test._count << endl << test._data.size() << endl;
for(auto i= test._data.rbegin();i != test._data.rend();++i){cout<< *i<< ":";}
}
On a general note, the main issue here is that you will need one instance of your vector
per enum (logic, isn't it ?).
Your correction does not work if you use 2 enums for example.
So the logical thing to do would be something along the line of:
template <class Enum>
struct sp_enum_vector { static std::vector<unsigned> _data; };
And then modify your enum class as:
template <class Enum, unsigned... values>
struct sp_enum;
in order to differentiate the class depending on the enum we are talking about.
Next question: how do we get the unsigned
out of t
? It's bad to hardcode that :p
精彩评论