std :: tuple как замена члена, вспомогательный макрос - PullRequest
0 голосов
/ 10 июня 2018

Я недавно начал использовать кортежи вместо простых учеников, потому что мне удобно с ними работать.Мой код выглядит примерно так:

class TestClass final {
public:
   TestClass() = default;
   ~TestClass() = default;

public:
   template<int M>
   auto get()->decltype(std::get<M>(m_private_members)) const {
      return std::get<M>(m_private_members);
   }

   enum PrivateIdx {
      count,
      use_stuff,
      name
   };

private:
   std::tuple<int, bool, std::string> m_private_members{1, true, "bla"};

};

Так что теперь это можно использовать так:

   std::cout << t.get<TestClass::name>()> << std::endl;

Это тоже хорошо работает - единственное, добавление членов может быть довольно ошибочным-prone.Можно легко получить неверные перечисления доступа, перепутав порядок или забыв член.Я думал о такой вещи в стиле макроса, как:

   PUBLIC_MEMBERS(
      MEMBER(int count),
      MEMBER(std::string name)
   );

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

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

Ответы [ 3 ]

0 голосов
/ 10 июня 2018

Тем временем я придумал что-то, используя var args ...

taken from
[https://stackoverflow.com/questions/16374776/macro-overloading][1]
#define EXPAND(X) X 
#define __NARG__(...)  EXPAND(__NARG_I_(__VA_ARGS__,__RSEQ_N()))
#define __NARG_I_(...) EXPAND(__ARG_N(__VA_ARGS__))
#define __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 __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

// general definition for any function name
#define _VFUNC_(name, n) name##n
#define _VFUNC(name, n) _VFUNC_(name, n)
#define VFUNC(func, ...) EXPAND(_VFUNC(func, EXPAND( __NARG__(__VA_ARGS__))) (__VA_ARGS__))


#define MEMBER_LIST(...) EXPAND(VFUNC(MEMBER_LIST, __VA_ARGS__))

#define MEMBER_LIST3(mem_type1, mem_name1, default_value1)\
\
enum PrivateIdx { \
   mem_name1 \
}; \
\
std::tuple<mem_type1> m_private_members{default_value1} 

#define MEMBER_LIST6( mem_type0, mem_name0, default_value0,\
                     mem_type1, mem_name1, default_value1)\
\
enum PrivateIdx { \
   mem_name0, \
   mem_name1 \
}; \
\
std::tuple< mem_type0, \
            mem_type1 > m_private_members{ default_value0, \
                                            default_value1}
..and so on

Работает, но imho все еще недостаточно элегантно.Я думаю, что я указал в правильном направлении.

0 голосов
/ 10 июня 2018

Интересная проблема.Мне любопытно, почему вы хотите это сделать.Это то, что я придумал.Хорошие новости: никаких макросов!

Основная проблема, я думаю, заключается в том, что вы хотите объявить идентификаторы для доступа к членам.Это не может быть решено с помощью шаблонов, поэтому вы должны либо: а) использовать макросы, либо б) как-то объявить эти идентификаторы напрямую.Вместо использования констант / перечислений я попытался использовать имена типов для идентификации члена в get.

. Начну с примера использования:

class User
{
public:
    enum class AccessLevel
    {
        ReadOnly,
        ReadWrite,
        Admin
    };

    struct Name : MemberId<std::string> {};
    struct Id : MemberId<unsigned> {};
    struct Access : MemberId<AccessLevel> {};

    template<typename MemberType>
    auto& get() { return PrivMembers::getFromTuple<MemberType>(m_members); }

    template<typename MemberType>
    const auto& get() const { return PrivMembers::getFromTuple<MemberType>(m_members); }

private:
    using PrivMembers = MembersList<Name, Id, Access>;

    PrivMembers::Tuple m_members;
};

int main()
{
    User user;
    user.get<User::Name>() = "John Smith";
    user.get<User::Id>() = 1;
    user.get<User::Access>() = User::AccessLevel::ReadWrite;

    return 0;
}

Name,Id и Access используются для идентификации элементов кортежа m_members.Эти структуры не имеют никаких членов сами.PrivMembers::Tuple это псевдоним для std::tuple<std::string, unsigned, AccessLevel>:

template<typename Type_>
struct MemberId { using Type = Type_; };

template<typename... Types>
struct MembersList
{
    using Tuple = std::tuple<typename Types::Type...>;

    template<typename T>
    static auto& getFromTuple(Tuple& tp) { return std::get<detail::IndexOf<T, Types...>::value>(tp); }

    template<typename T>
    static const auto& getFromTuple(const Tuple& tp) { return std::get<detail::IndexOf<T, Types...>::value>(tp); }
};

Первое: Tuple псевдоним.Я думаю, что самоочевидно, что происходит.Затем существуют перегрузки для getFromTuple, который используется классом User.При использовании MemberId -обработанных типов вместо констант для доступа к элементам кортежа, мне нужно найти индекс, соответствующий данному члену Id.Вот что происходит в getFromTuple.Есть вспомогательный класс, который выполняет поиск:

namespace detail
{
    template<typename Needle, typename HaystackHead, typename... Haystack>
    struct IndexOf { static constexpr std::size_t value = IndexOf<Needle, Haystack...>::value + 1; };

    template<typename Needle, typename... Haystack>
    struct IndexOf<Needle, Needle, Haystack...> { static constexpr std::size_t value = 0; };
}

Все это решает проблему необходимости поддерживать индексы для каждого члена, как в исходном решении.Синтаксис для объявления идентификаторов членов (struct Name : MemberId<std::string> {};) может быть немного раздражает, но я не могу думать о более компактном решении.

Все это работает с C ++ 14.Если вы можете жить с конечным типом возврата для User::get, то вы можете скомпилировать его как C ++ 11.

Вот полный код.

0 голосов
/ 10 июня 2018

Как я уже сказал в комментарии, макросы - это боль для отладки.Тот, кто не видит, как писать некоторые, должен подумать дважды, если использовать их вообще.OTOH их относительно просто написать, когда вы поймете логику этих действий.

Обратите внимание, что данное является лишь одним из способов сделать это, как и во всем, где их несколько.Итак, макросы выглядят так:

#define GET_NAME(NAME,TYPE,VALUE) NAME
#define GET_TYPE(NAME,TYPE,VALUE) TYPE
#define GET_VALUE(NAME,TYPE,VALUE) VALUE

#define DECLARE_ENUM(PRIVATES) \
    enum PrivateIdx { \
        PRIVATES(GET_NAME) \
    };

#define DECLARE_TUPLE(PRIVATES) \
    std::tuple<PRIVATES(GET_TYPE)> m_private_members{PRIVATES(GET_VALUE)};

#define DECLARE_IN_ONE_GO(PRIVATES) \
    public: \
        DECLARE_ENUM(PRIVATES) \
    private: \
        DECLARE_TUPLE(PRIVATES)

И использование выглядит так:

#include <iostream>
#include <tuple>
#include "enum_tuple_macros.h"

class TestClass final {
public:
    TestClass() = default;
    ~TestClass() = default;

    #define PRIVATES(MEMBER) \
        MEMBER(count,int,1), \
        MEMBER(use_stuff,bool,true), \
        MEMBER(name,std::string,"bla")

    DECLARE_IN_ONE_GO(PRIVATES)

    // note that the get can be also generated by DECLARE_IN_ONE_GO
public:
    template<int M>
    auto get() const -> decltype(std::get<M>(m_private_members)) {
        return std::get<M>(m_private_members);
    }
};

int main()
{
    TestClass t;
    std::cout << t.get<TestClass::name>() << " in one go" << std::endl;
}

Кажется, работает на gcc 8.1.0, который я пробовал.

...