【问题标题】:Structure or Class with variable number of members具有可变成员数的结构或类
【发布时间】:2011-04-19 16:41:53
【问题描述】:

我想创建一个具有可变数量的类成员的结构/类,这些成员可以在编译阶段决定(就像在模板元编程中所做的那样)

示例:假设类型和变量名都被指定,例如类型 T1 变量名应该是 varName1 等等.....

template <class T1 (varName1) >
MyClass
{
     T1 varName1;

}

template <class T1 (varName1), class T2 (varName2) >
MyClass
{
     T1 varName1;
     T1 varName2;
}

在主代码中可以像下面这样声明或其他可以指定类型和名称的方式

我的类对象;

和 MyClass::somefunc() 可以访问变量名 如下

MyClass::somefunc()
{
     std::cout <<" abc value : " << abc << std::endl;
     std::cout <<" xyz value : " << xyz << std::endl;
}

这是否可以通过 C++ 中的模板元编程来同时指定类型和变量名?

【问题讨论】:

    标签: c++ templates metaprogramming


    【解决方案1】:

    模板不能指定变量名。如果您在编译时决定在类中包含什么,您应该能够直接在源代码中指定它。

    你也许可以用一些宏来完成你想要的,但我不敢冒险进入那个黑暗的领域。

    【讨论】:

      【解决方案2】:

      您不能使用模板指定名称,只能使用类型或某些类型的值。您也许可以使用宏来做到这一点,但是试图在语言中做太多事情是我陷入太多次的陷阱。还有另一条路径可能对您有用:代码生成

      考虑编写一个脚本来读取一些配置并吐出你的类的定义。将脚本添加到您的构建过程中。这可能比模板元编程或宏诡计的黑魔法更容易维护和理解。

      python 是我在脚本中使用的,每个类的配置都像 json 一样易于解析 - 但这些都是附带问题

      在我当前的项目中,我们有数千行生成的文件,分布在 100 多个文件中……并且这些生成脚本的修改相对定期且轻松。

      【讨论】:

        【解决方案3】:

        我记得 Andrei Alexandrescu 在他的《现代 C++》一书中描述了类似的东西。我这里没有副本,所以我不能确切地知道它是什么以及在哪里。

        正如其他人指出的那样,不可能将名称作为模板参数,但他创建了一个可以像data.get&lt;T1&gt;() 或类似的东西访问的结构。如果一种类型的数据不止一个,您可以使用data.get&lt;T1,2&gt;()

        也许这有帮助。

        【讨论】:

          【解决方案4】:

          你可以做类似的事情,但他们不会有不同的名字:

          template <class T, int num_t >
          MyClass
          {
               T var[num_T];
          };
          

          这忽略了边界检查,但这是另一回事。

          【讨论】:

          • 没关系,但类型可以不同,我必须通过不同的名称访问 :)
          【解决方案5】:

          如所述,不可能。使用 boost 的预处理器库,您可能会获得等效的功能。

          最终,您所要求的与简单地说...

          struct Members
          {
              int a_;
              double b_;
          };
          

          ...进入...

          template <class Members>
          class Add_Stuff : public Members
          {
            public:
              doSomething() { ... };
          };
          

          ...因为 doSomething 被赋予了迭代和打印成员的能力,对吧?

          您还可以编写一个简单的程序/脚本来读取类型和标识符的列表并输出您需要的 C++ 代码。如果您有很多领域要处理,这可能是一个好方法。作为一个最小的头顶轮廓,并假设输入像这样,换行符强制执行简单的类型与标识符划分(让您为数组等创建 typedef):

          std::string
          idn1
          const int*
          idn2
          my_typedef
          ind3
          

          ...你可以生成一些 C++ 代码 ala...

          std::ostringstream streaming;
          streaming << "    void somefunc() const\n{\n    std::cout ";
          
          cout << "class " << class_name << "\n{\n";
          while (cin.getline(type) && cin.getline(identifier))
          {
              cout << "    " << type << ' ' << identifier << '\n';
              streaming << "<< \"" << identifier << " \" << identifier << "\n        ";
          }
          cout << "  public:\n" << streaming.str() << "\n"
                  "};\n";
          

          显然,您可以清理输入以允许更自然的类型和标识符的 C++ 表达式并使解析逻辑复杂化 - 正则表达式可能足以满足您的需求,或者您可以尝试精神或自己做。

          预处理器骇客可以直接在 C++ 中实现类似的功能,但恕我直言,编写和维护它会更加丑陋和耗时。

          如果您实际上不需要通过 identifier 访问成员,则可以按照 TokenMacGuy 的建议进行操作,前提是每个字段都可以具有相同的类型(还不错 - 考虑 boost::variant 或 ~ ::any),或者如果您可以确保每个字段都有不同的类型(同样,这可以通过简单的包装模板类强制执行),还有另一种选择:我称之为“类型映射” - 您可以将类型用作键入有效的类型不同值的关联容器,在编译时解决所有查找并支持您的 somefunc() 实现所需的自动迭代。如果需要,可以将其与运行时类型命名的字符串结合起来,但无法实现在编译时解析或验证的标识符字符串。

          我可能在 6 年前实现了这样的功能(在 Alexandrescu 的 Loki 库上使用类型列表),并在 boost 邮件列表中询问是否有人感兴趣,但没有人看到它的实用性,我并没有真正尝试解释。它实际上对日志系统非常有用,这促使我首先编写它。无论如何,我怀疑我没有费心将代码发布到那个库,并且没有方便,所以你需要从头开始,除非 MPL 或其他一些库已经实现了他们自己的类似“容器”同时(或事先......?)。

          【讨论】:

          • 是的,你是对的。我也想过从结构派生并生成具有不同成员变量名称的结构的类似行没有点击。这些类型可以不同,按名称访问变量是我的实现中最需要的部分。
          • @Pardeep:我明白了。然后您可能想查看 boost 序列化库 - 我认为它具有预处理器宏来捕获有关成员的一些额外元数据,您可以在实现自己的函数 ala doSomething()... 时使用它们来迭代它们。
          【解决方案6】:

          Loki 的类型列表。 link text 对我来说相当复杂。但我认为你想要的可以用这个来完成。

          【讨论】:

            【解决方案7】:

            看看std::tuple

            这允许任意(但在编译时固定)数量的数据元素,以及每个索引的类型安全访问。

            【讨论】:

              【解决方案8】:

              我发现这个问题没有明确说明,不清楚目的是什么。

              对于序列化,我会考虑the Boost library's serialization support

              对于命名的、严格类型的可选参数,一种可能是使用Boost parameters library,第二种更易于使用的可能是my own options pack support。它本质上是一组宏,通过一些难以理解的内部模板黑魔法,生成您要求的类。我在 Dobbs 博士杂志上写了一篇关于它的文章,但这里有一个使用示例说明了一个主要优点,即生成的选项类可以与另一个类层次结构并行扩展:

              #include <iostream>
              #include <progrock/cppx/arguments/options_boosted.h>
              
              struct AbstractButton
              {
                  // These members are not part of the cppx options scheme: in actual
                  // usage you will instead have e.g. some API level widget states.
                  int     hTextAlign;
                  int     vTextAlign;
                  int     buttonPlacement;
              
                  // Defines a local class 'Options' with specified options & defaults.
                  CPPX_DEFINE_OPTIONCLASS( Options, CPPX_OPTIONS_NO_BASE,
                      ( hTextAlign,       int,        0   )
                      ( vTextAlign,       int,        0   )
                      ( buttonPlacement,  int,        0   )
                      )
              
                  explicit AbstractButton( Options const& params = Options() )
                      : hTextAlign( params.hTextAlign() )
                      , vTextAlign( params.vTextAlign() )
                      , buttonPlacement( params.buttonPlacement() )
                  {}
              };
              
              struct CheckBox: AbstractButton
              {
                  bool    isAuto;
                  bool    is3State;
              
                  // Defines an extension of the base class' 'Options' class.
                  CPPX_DEFINE_OPTIONCLASS( Options, AbstractButton::Options,
                      ( isAuto ,          bool,       true    )
                      ( is3State,         bool,       false   )
                      )
              
                  explicit CheckBox( Options const& params = Options() )
                      : AbstractButton( params )
                      , isAuto( params.isAuto() )
                      , is3State( params.is3State() )
                  {}
              };
              
              void show( CheckBox const& cb )
              {
                  std::cout
                      << std::boolalpha
                      << "hTextAlign = " << cb.hTextAlign
                      << ", isAuto = " << cb.isAuto << ".\n";
              }
              
              int main()
              {
                  typedef CheckBox::Options   CBOptions;
              
                  CheckBox        widget1;
                  show( widget1 );                // 0, true (the default values)
              
                  CheckBox        widget2( CBOptions().hTextAlign( 1 ) );
                  show( widget2 );                // 1, true
              
                  CheckBox        widget3( CBOptions().hTextAlign( 1 ).isAuto( false ) );
                  show( widget3 );                // 1, false
              }
              

              上面的代码使用一些未记录的 Boost 魔法来提供 C++98 可变参数宏。 :-)

              还有一组更基本的非 Boosted 宏,它消除了 Boost 依赖性,代价是必须指定每个生成的类中的成员数。

              干杯,

              --阿尔夫

              【讨论】:

                【解决方案9】:

                使用模板元编程和少量预处理,可以实现接近理想的语法:

                //one has to "declare" once an attribute name to be able to use
                //it later in any number of class declarations
                DECLARE_ATTRIBUTE_NAME(foo);
                DECLARE_ATTRIBUTE_NAME(quux);
                DECLARE_ATTRIBUTE_NAME(bar);
                DECLARE_ATTRIBUTE_NAME(baz);
                
                //pass types and declared attribute names, separated by comma
                typedef TupleWithNamedMembers<int, foo,
                                              float, quux,
                                              double, bar,
                                              char, baz
                                        > MyTuple;
                //extra call to macro "MAKE_TUPLE" can be avoided, see below
                class MyConstruct: public MAKE_TUPLE(MyTuple)
                { };
                
                //usage
                int main(int argc, char* argv[])
                {
                    MyConstruct construct;
                    construct.foo = 3;
                    construct.bar = 5.6;
                    construct.quux = 8.9;
                    construct.baz = 'h';
                    return 0;
                }
                

                实现:

                #ifndef TupleWithNamedMembersH
                #define TupleWithNamedMembersH
                //---------------------------------------------------------------------------
                
                #include <Loki/typelist.h>
                #include <Loki/HierarchyGenerators.h>
                
                template<class T, int a>
                struct attribute
                {
                };
                
                //the generated id is not really unique in all cases
                //one should provide better implementation
                #define GENERATE_UNIQ_ID(name) ((sizeof(#name)<<16)|__LINE__)
                
                //specializations of the struct "attribute" act like compile-time map between
                //a name ID and an attribute name 
                #define DECLARE_ATTRIBUTE_NAME_IMPL(name, id) \
                    enum { id = GENERATE_UNIQ_ID(name) }; \
                    template<class T> \
                    struct attribute<T,id> \
                    {\
                        T name;\
                    };
                #define DECLARE_ATTRIBUTE_NAME(name)\
                    DECLARE_ATTRIBUTE_NAME_IMPL(name, name)
                
                //helps to pass pair of type and name ID as a single type
                template<class T, int i>
                struct pair
                {
                    static const int val = i;
                    typedef T type;
                };
                
                //unpacks compile-time data from PairT and inherits attribute
                //with name selected by ID
                template<class PairT>
                class holder: public attribute<typename PairT::type,PairT::val>
                {    };
                
                //turns template arguments into Loki::TypeList
                template
                <
                    typename T1  = Loki::NullType, int i1 = 0, typename T2  = Loki::NullType, int i2 = 0,
                    typename T3  = Loki::NullType, int i3 = 0, typename T4  = Loki::NullType, int i4 = 0,
                    typename T5  = Loki::NullType, int i5 = 0, typename T6  = Loki::NullType, int i6 = 0,
                    typename T7  = Loki::NullType, int i7 = 0, typename T8  = Loki::NullType, int i8 = 0,
                    typename T9  = Loki::NullType, int i9 = 0, typename T10 = Loki::NullType, int i10 = 0
                >
                struct TupleWithNamedMembers
                {
                public:
                    typedef Loki::TL::MakeTypelist<pair<T1,i1>, pair<T2,i2>,
                                                   pair<T3,i3>, pair<T4,i4>,
                                                   pair<T5,i5>, pair<T6,i6>,
                                                   pair<T7,i7>, pair<T8,i8>,
                                                   pair<T9,i9>, pair<T10,i10>
                                         >::Result Result;
                };
                
                //this macro is required because of internal compiler error that I encounter
                //Loki::GenScatterHierarchy makes a class inherit every attribute from the type list
                #define MAKE_TUPLE(types) Loki::GenScatterHierarchy<types::Result, holder>
                
                #endif //end of "TupleWithNamedMembers.h"
                

                注意事项: 如果您的编译器可以使用以下代码,则 MAKE_TUPLE 宏应该是一个元函数:

                template
                <
                    typename T1  = Loki::NullType, int i1 = 0, typename T2  = Loki::NullType, int i2 = 0,
                    typename T3  = Loki::NullType, int i3 = 0, typename T4  = Loki::NullType, int i4 = 0,
                    typename T5  = Loki::NullType, int i5 = 0, typename T6  = Loki::NullType, int i6 = 0,
                    typename T7  = Loki::NullType, int i7 = 0, typename T8  = Loki::NullType, int i8 = 0,
                    typename T9  = Loki::NullType, int i9 = 0, typename T10 = Loki::NullType, int i10 = 0
                >
                struct MakeTupleWithNamedMembers
                {
                private:
                    typedef Loki::TL::MakeTypelist<pair<T1,i1>, pair<T2,i2>,
                                                   pair<T3,i3>, pair<T4,i4>,
                                                   pair<T5,i5>, pair<T6,i6>,
                                                   pair<T7,i7>, pair<T8,i8>,
                                                   pair<T9,i9>, pair<T10,i10>
                                         >::Result type_list;
                public:
                    typedef Loki::GenScatterHierarchy<type_list, holder> Result;
                };
                
                //usage
                class MyConstruct: public MakeTupleWithNamedMembers<int, foo, float, quux>::Result
                { };
                

                【讨论】:

                • @Pardeep:解决方案改进:不需要下划线前缀。现在,我喜欢它的外观)
                • omg... 我们可以轻松地将用户指定的方法和变量名称注入到库和框架的代码中,而不仅仅是类型。瞧,抽象结构的组合与领域细节,如 BinaryTree (使用将非常自我记录)。
                猜你喜欢
                • 1970-01-01
                • 2011-11-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-06-22
                • 2012-03-02
                相关资源
                最近更新 更多