我最近开始使用元组而不是普通的类成员,因为我觉得使用它们很方便。所以我的代码看起来像这样:
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)
);
这将扩展为元组和枚举代码。问题是,我不认为这可以通过宏来解决,因为它必须扩展到两个不同的数据结构,对吗?此外,我必须承认,我从未研究过复杂的宏。
我还想到了一个解决这个问题的模板,但我也无法找到一个可行的解决方案,因为模板无法生成枚举。
答案 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;
}
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; };
}
所有这些解决了必须维护每个成员的索引的问题,就像在原始解决方案中一样。声明成员ID(struct Name : MemberId<std::string> {};
)的语法可能有点烦人,但我想不出更紧凑的解决方案。
所有这些都适用于C ++ 14。如果您可以使用User::get
的尾随返回类型,那么您可以将其编译为C ++ 11。
答案 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仍然不够优雅。我认为我指出了正确的方向。