具有可变数量成员的结构或类

时间:2010-10-01 04:59:04

标签: c++ templates metaprogramming

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

示例:假设其中类型和变量名称都要指定为类型T1变量名,应该是varName1等等.....

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

}

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

并且在主代码中可以声明如下或其他可以指定类型和名称的方式

MyClass Obj;

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

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

这是否可以通过C ++中的模板元编程实现类型和变量名称规范?

9 个答案:

答案 0 :(得分:5)

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

//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
{ };

答案 1 :(得分:3)

不可能如上所述。使用boost的预处理器库可能会获得相同的功能。

最终你所要求的不同于简单地传递说...

struct Members
{
    int a_;
    double b_;
};

... INTO ...

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

...因为doSomething有能力迭代并打印成员,对吧?

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

std::string
idn1
const int*
idn2
my_typedef
ind3

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

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 ++表达式并使解析逻辑复杂化 - 正则表达式可能足以满足您的需求,或者您可以尝试精神或自己动手。

预处理器hackery可以直接在C ++中实现类似的东西,但是恕我直言,编写和维护会更加丑陋和耗时。

如果您实际上不需要通过标识符访问成员,那么您可以执行TokenMacGuy建议的每个字段可以具有相同类型(不是那么糟糕) - 考虑boost :: variant或〜如果你可以确保每个字段都有一个不同的类型(再次,这可以通过简单的包装器模板类强制),那么还有另一个选项:我称之为“类型映射” - 你可以在这里使用类型键入有效的类型 - 不同值的关联容器,所有查找在编译时解析,并支持somefunc()实现所需的自动迭代。如果需要,可以将它与字符串组合用于运行时类型命名,但无法实现在编译时解析或验证的标识符字符串。

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

答案 2 :(得分:2)

您不能使用模板,仅使用类型或某些类型的值来指定名称。您可以使用宏来完成它,但是在语言中尝试做太多是我陷入了太多次的陷阱......还有另一条路可能对您有用:代码生成

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

python是我用于脚本的内容,每个类的配置都像json一样容易解析 - 但这些是副作用

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

答案 3 :(得分:1)

我记得Andrei Alexandrescu在他的书“Modern C ++”中描述了类似的东西。我没有这里的副本,所以我无法准确说出它是什么以及它在哪里。

正如其他人所指出的那样,不可能将名称作为模板参数,但是他创建了一个可以像data.get<T1>()那样访问的结构。如果一种类型的数据不止一个,您可以data.get<T1,2>()

也许这有帮助。

答案 4 :(得分:1)

我发现问题没有明确,目前还不清楚目的是什么。

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

对于命名的,严格类型化的可选参数,一种可能性是使用Boost parameters library,第二种更简单的可选参数是my own options pack support。它本质上是一组宏,通过一些不可穿透的内部模板黑魔法,生成像你要求的类。我在Dr. Dobbs Journal上写了一篇关于它的文章,但是这里有一个用例说明了一个主要优点,即生成的选项类可以与另一个类层次结构并行扩展:

#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依赖性,代价是必须指定每个生成的类中的成员数。

干杯&amp;第h。,

- Alf

答案 5 :(得分:0)

模板无法指定变量名称。如果你在编译时决定在课程中有什么,你应该能够直接在源代码中指定它。

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

答案 6 :(得分:0)

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

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

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

答案 7 :(得分:0)

Loki的打字清单。 link text 对我来说相当复杂。但我认为你想要的就是用这个来完成的。

答案 8 :(得分:0)

看看std::tuple

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