关于可变参数模板

发布于 2024-08-28 11:17:01 字数 4925 浏览 6 评论 0原文

我目前正在体验新的 c++0x 可变参数模板,它非常有趣,尽管我对成员实例化的过程有疑问。

在此示例中,我尝试模拟强类型枚举,并可以选择随机有效的强枚举(这用于单元测试)。


#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<< ":";}
}



我用这段代码得到的结果是:

3
1
5:

有人能指出我在这里弄乱了什么吗???

Ps:使用 gcc 4.4.3,


我重新编写了代码,使其更加通用,并尽可能减少硬核编码(@Matthieu M.)。但我想解释一下为什么我之前要做这一切。

正如许多开发人员在我的代码中接受了新的 c++0x 标准一样,我对此感到非常高兴。但在尝试编写测试单元时,我遇到了强类型枚举的问题。

问题是你不能生成一个随机的强类型枚举(我知道你可以,但想以更优雅的方式做到这一点)。因此,现在有了这段代码,我可以使用可变参数模板和可变参数宏(是的旧脏宏)声明并随机选择一个强类型和作用域枚举。

这是代码:


#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;
}

现在困扰我的是 PP_NARG 宏的限制(我还没有找到任何其他方法来计算参数的数量)。

我很乐意接受任何加强这一点的指示或暗示。

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 strongly 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

挥剑断情 2024-09-04 11:17:01

静态向量_data; 不在 sp_enum 实例化之间共享,仅在具有相同参数的模板类实例之间共享。

static vector<unsigned> _data; is not shared across sp_enum instantiations, only across instances of the template class with the same parameters.

给我一枪 2024-09-04 11:17:01

既然这个问题已经变得一团糟了。

你在追求这样的东西吗?抱歉,我不知道 boost 的预处理器库,但它似乎并不是特别必要。唯一的问题是,count 必须是编译时常量吗?

#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;
}

诚然,设计可以更好,而且我没有担心默认的想法(无论如何,如果您不想要连续的值,它就会崩溃)。


另一件事,如果这仅支持连续值,那么首先就没有理由使用向量。只需存储第一个值(如果不总是 0)和最后一个值,其他所有值都可以计算。 data 方法可能会返回 boost 的 counting_iterator 范围。

或者只是专门化一个相应的特征类:

#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';
}

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';
}
想念有你 2024-09-04 11:17:01

感谢帕维尔,我解决了这个问题。

这是解决方案


#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<< ":";}
}

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<< ":";}
}

蒗幽 2024-09-04 11:17:01

一般来说,这里的主要问题是每个枚举都需要一个 vector 实例(逻辑,不是吗?)。

例如,如果您使用 2 个枚举,您的更正将不起作用。

因此,合乎逻辑的做法是:

template <class Enum>
struct sp_enum_vector { static std::vector<unsigned> _data; };

然后将您的枚举类修改为:

template <class Enum, unsigned... values>
struct sp_enum;

以便根据我们正在讨论的枚举来区分该类。

下一个问题:我们如何从 t 中获取 unsigned ?硬编码是不好的:p

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文