Pardeep
Pardeep

Reputation: 949

Structure or Class with variable number of members

I want to create a structure/class with a variable number of class members which could be decided at compilation stage (like done in template metaprogramming)

Example : Its hypothetical in which both type and variable names are to be specified like Type T1 variable name should be varName1 and so on .....

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

}

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

and in main code which can be declared like following or some other way in which type and name can be specified

MyClass Obj;

and MyClass::somefunc() can access variable names as follows

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

Is this possible via template metaprogramming in C++ to have both type and variable name specification ?

Upvotes: 5

Views: 6476

Answers (9)

Alsk
Alsk

Reputation: 1118

With template metaprogramming and little preprocessing, it is possible to achieve a syntax close to desirable:

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

The implementation:

#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"

Notes: The MAKE_TUPLE macro should be a metafunction, if your compiler is OK with the code below:

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

Upvotes: 5

Philipp
Philipp

Reputation: 11813

I remember that Andrei Alexandrescu describes something similar in his book "Modern C++". I do not have a copy here so I can't tell exactly what and where it was.

As others have pointed out, it is not possible to have names as template arguments, but he created a struct which could be accessed like data.get<T1>() or something like that. If there were more than one data of one type you could do data.get<T1,2>().

Maybe that helps.

Upvotes: 1

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145359

I find the question underspecified, it's not clear what the purpose is.

For serialization I'd consider the Boost library's serialization support.

For named, strictly typed optional arguments one possibility is to use the Boost parameters library, and a second, simpler-to-use possibility is my own options pack support. It's essentially a set of macros that, via some impenetrable internal template black magic, generates classes like you ask for. I wrote an article in Dr. Dobbs Journal about it, but here's a usage example illustrating a main advantage, that the generated option classes can be extended in parallel with another class hierarchy:

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

The above code uses some undocumented Boost magic to provide C++98 variadic macros. :-)

There's also a more basic un-Boosted set of macros which removes the Boost dependency, at the cost of having to specify the number of members in each generated class.

Cheers & hth.,

– Alf

Upvotes: 1

sbi
sbi

Reputation: 224119

Have a look at std::tuple.

This allows an arbitrary (but fixed at compile-time) number of data elements, and type-safe access per index.

Upvotes: 0

Manoj R
Manoj R

Reputation: 3247

Loki's typelist. link text Quite complicated for me. But I think what you want can be done using this.

Upvotes: 0

Tony Delroy
Tony Delroy

Reputation: 106196

Not possible as described. You might get equivalent functionality using boost's preprocessor library.

Ultimately what you're asking for differs from simply passing say...

struct Members
{
    int a_;
    double b_;
};

...into...

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

...in that doSomething is given the ability to iterate over and print the members, right?

You can also write a simple program/script that reads a list of types and identifiers and outputs the C++ code you require. If you have a lot of fields to deal with, this is probably a good approach. As a minimal off-the-top-of-my-head outline, and assuming input like so the newlines enforce a simple type vs identifier division (making you create typedefs for arrays etc):

std::string
idn1
const int*
idn2
my_typedef
ind3

...you can generate some C++ code 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";

Obviously you could clean up the input to allow a more natural C++ expression of types and identifiers and complicate the parsing logic - a regexp may be good enough for your needs, or you could try spirit or do it yourself.

Preprocessor hackery can achieve something similar directly inside C++, but IMHO it will be even uglier and more time consuming to write and maintain.

If you don't actually need to access the members by identifier, you might do what TokenMacGuy suggests if every field can have the same type (not so bad - consider boost::variant or ~::any), or there's another option if you can ensure every field has a distinct type (again, this can be forced via trivial wrapper template classes): what I call a "type map" - where you can use the type as a key into what's effectively an associative container of type-distinct values, with all lookup resolved at compile time and support for the automatic iteration needed for your somefunc() implementation. Could combine that with strings for run-time type naming if desired, but can't achieve identifier strings that are resolved or validated at compile time.

I implemented such maybe 6 years ago (atop Alexandrescu's Loki library using type lists) and asked on the boost mailing list whether anyone was interested, but nobody saw the utility of it and I didn't really try to explain. It's actually very useful for logging systems, which prompted me to write it in the first place. Anyway, I suspect I didn't bother to post code to the vault for that one, and don't have it handy, so you'd need to start from scratch unless MPL or some other library has implemented their own similar "container" meanwhile (or beforehand...?).

Upvotes: 3

SingleNegationElimination
SingleNegationElimination

Reputation: 156238

You can do something similar, but they won't have distinct names:

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

This overlooks bounds checking, but that's another story.

Upvotes: 0

Michael Anderson
Michael Anderson

Reputation: 73520

You can't specify names using templates, only types or certain kinds of values. You may be able to do it using macros, however trying to do too much within the language is a trap I have fallen into too many times .. there is another path that may work for you : code generation

Consider writing a script to read in some config and spit out the definitions of your classes. Add the script to your build process. This may be easier to maintain and understand than the black arts of template metaprogramming or macro skulduggery.

python is what I'd use for the script, with per class configuration in something easy to parse like json - but those are side issues

In my current project we have thousands of lines of generated files, spread across over 100 files... and those generation scripts are modified relatively regularly, and painlessly.

Upvotes: 2

JoshD
JoshD

Reputation: 12814

Templates can't specify variable names. If you're deciding at compilation time what to have in the class, you should be able to specify it directly in source code.

You may be able to accomplish what you want with some macros, but I dare not venture into that dark territory.

Upvotes: 0

Related Questions