std :: tuple作为成员替换,方便宏

时间:2018-06-10 09:39:10

标签: c++ stdtuple

我最近开始使用元组而不是普通的类成员,因为我觉得使用它们很方便。所以我的代码看起来像这样:

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;

这项工作也很好 - 唯一的一点是,添加成员可能非常容易出错。通过混合订单或忘记成员,可以很容易地使访问枚举错误。我在想一个宏观风格的东西:

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

这将扩展为元组和枚举代码。问题是,我不认为这可以通过宏来解决,因为它必须扩展到两个不同的数据结构,对吗?此外,我必须承认,我从未研究过复杂的宏。

我还想到了一个解决这个问题的模板,但我也无法找到一个可行的解决方案,因为模板无法生成枚举。

3 个答案:

答案 0 :(得分:2)

有趣的问题。我很好奇你为什么要这样做。这是我想出来的。好消息:没有宏!

我认为,主要问题是您要声明标识符以访问成员。这不能通过模板解决,因此您必须a)使用宏,或b)以某种方式直接声明这些标识符。我尝试使用类型名称来识别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;
}

NameIdAccess用于标识m_members元组的元素。这些结构本身没有任何成员。 PrivMembers::Tuplestd::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; };
}

所有这些解决了必须维护每个成员的索引的问题,就像在原始解决方案中一样。声明成员ID(struct Name : MemberId<std::string> {};)的语法可能有点烦人,但我想不出更紧凑的解决方案。

所有这些都适用于C ++ 14。如果您可以使用User::get的尾随返回类型,那么您可以将其编译为C ++ 11。

Here's full code.

答案 1 :(得分:1)

就像我在评论中说的那样,调试很痛苦。没有看到如何写一些的人应该三思而后行。 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上工作。

答案 2 :(得分:0)

与此同时,我想出了一些使用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仍然不够优雅。我认为我指出了正确的方向。