Структура или класс с переменным числом членов - PullRequest
4 голосов
/ 01 октября 2010

Я хочу создать структуру / класс с переменным числом членов класса, который может быть решен на этапе компиляции (как это сделано в метапрограммировании шаблона)

Пример: гипотетически, в котором имена типов и переменных должны указываться так же, как имя переменной типа 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 ]

5 голосов
/ 08 октября 2010

С помощью метапрограммирования шаблонов и небольшой предварительной обработки можно получить синтаксис, близкий к желаемому:

//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
{ };
3 голосов
/ 01 октября 2010

Не возможно, как описано. Вы можете получить эквивалентную функциональность, используя библиотеку препроцессора 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 ++ ...

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 ++, но, по-моему, это будет еще уродливее и займет больше времени при написании и обслуживании.

Если вам на самом деле не нужен доступ к членам по идентификатору , вы можете сделать то, что предлагает TokenMacGuy, если каждое поле может иметь один и тот же тип (не так уж и плохо - рассмотрите boost :: option или ~: : любой), или есть другой вариант, если вы можете убедиться, что каждое поле имеет отдельный тип (опять же, это может быть принудительно введено через тривиальные классы шаблонов-оболочек): то, что я называю «картой типов» - где вы можете использовать тип в качестве ключа в то, что фактически является ассоциативным контейнером значений, отличных от типа, со всеми поисками, разрешенными во время компиляции, и поддержкой автоматической итерации, необходимой для вашей реализации somefunc (). При желании может комбинировать это со строками для именования типов во время выполнения, но не может получить строки идентификаторов, которые разрешаются или проверяются во время компиляции.

Я реализовал такое, может быть, 6 лет назад (поверх библиотеки Локи Александреску с использованием списков типов) и спросил в списке рассылки надстройки, заинтересован ли кто-нибудь, но никто не видел его полезности, и я не пытался объяснить. Это на самом деле очень полезно для систем журналирования, что побудило меня написать его в первую очередь. В любом случае, я подозреваю, что я не удосужился отправить код в хранилище для этого, и у меня его нет под рукой, поэтому вам придется начинать с нуля, если MPL или какая-либо другая библиотека не реализовали свой собственный подобный «контейнер». тем временем (или заранее ...?).

2 голосов
/ 01 октября 2010

Вы не можете указывать имена, используя шаблоны, только типы или определенные виды значений. Возможно, вы сможете сделать это с помощью макросов, однако попытка сделать слишком много в языке - это ловушка, в которую я попадал слишком много раз ... Есть другой путь, который может работать для вас: генерация кода

Подумайте над тем, чтобы написать скрипт для чтения в некоторых конфигурациях и выложить определения ваших классов. Добавьте скрипт в ваш процесс сборки. Это может быть проще в поддержании и понимании, чем черное искусство шаблонного метапрограммирования или макроделения.

Python - это то, что я использовал бы для сценария, с конфигурацией для каждого класса в чем-то, что легко разбирать, как в json - но это побочные проблемы

В моем текущем проекте у нас есть тысячи строк сгенерированных файлов, распределенных по более чем 100 файлам ... и эти сценарии генерации изменяются относительно регулярно и безболезненно.

1 голос
/ 08 октября 2010

Я считаю вопрос не конкретизированным, неясно, для чего это делается.

Для сериализации я бы рассмотрел Поддержка сериализации библиотеки Boost .

Для именованных,Строго типизированные необязательные аргументы. Одна возможность - использовать библиотеку параметров Boost , а вторая, более простая в использовании возможность - моя собственная поддержка пакета опций .По сути, это набор макросов, которые с помощью какого-то непроходимого внутреннего шаблона черной магии генерируют классы, как вы просили.Я написал статью в 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
}

Приведенный выше код используетнемного недокументированной магии Повышения для обеспечения макроса C ++ 98 variadic.: -)

Существует также более простой набор макрокоманд без Boosted, который удаляет зависимость Boost за счет необходимости указывать количество членов в каждом создаваемом классе.

Cheers &hth.,

- Alf

1 голос
/ 01 октября 2010

Я помню, что Андрей Александреску описывает нечто подобное в своей книге «Современный С ++». У меня нет копии здесь, поэтому я не могу точно сказать, что и где это было.

Как уже отмечали другие, невозможно иметь имена в качестве аргументов шаблона, но он создал структуру, к которой можно получить доступ, например, data.get<T1>() или что-то подобное Если бы было несколько данных одного типа, вы могли бы сделать data.get<T1,2>().

Может быть, это поможет.

0 голосов
/ 07 октября 2010

Посмотрите на std::tuple.

Это допускает произвольное (но фиксированное во время компиляции) количество элементов данных и безопасный тип доступа для каждого индекса.

0 голосов
/ 07 октября 2010

список Локи. текст ссылки Довольно сложно для меня.Но я думаю, что вы можете сделать это с помощью этого.

0 голосов
/ 01 октября 2010

Вы можете сделать что-то похожее, но у них не будет разных имен:

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

Это пропускает проверку границ, но это уже другая история.

0 голосов
/ 01 октября 2010

Шаблоны не могут указывать имена переменных.Если вы решаете во время компиляции, что иметь в классе, вы сможете указать это непосредственно в исходном коде.

Возможно, вы сможете добиться того, чего хотите, с помощью некоторых макросов, но я не осмелюсьрискнуть в эту темную территорию.

...