繁体   English   中英

基于可变参数模板的类成员变量

[英]Class member variables based on variadic template

给定一个类型,名称和默认值的列表,我可以轻松编写一个生成有效c ++代码的工具,该代码声明一个具有每种类型,名称和默认值的成员变量的类。 例如,给出列表

  • int,foo,42
  • 漂浮,酒吧,0.1f

(和类名“Baz”),它会生成

class Baz {
    int foo = 42;
    float bar = 0.1f;
}

如果一个工具可以生成这样一个类,编译器不能为我做这个吗? 我正在考虑这些方面的事情(注意:这是伪代码):

template <typename ...MemberTypes> class Baz {
    MemberTypes::type... MemberTypes::name... = MemberTypes::default...;
}

上面的类将被创建类似的东西

using MyBaz = Baz<member_type<int, "foo", 42>, member_type<float, "bar", 0.1f>>;

可能出现这种情况的原因:

  • 所有必需的信息都可在编译时获得。 外部工具可以轻松完成。
  • 可以以类似的方式创建具有元组而不是专用成员变量的类( 从variadic模板参数声明成员变量 )。
  • 我可以使用模板特化来近似成员的有限组合。
  • 模板元编程是图灵完备的( C ++模板Turing-complete? ),所以“一切”应该是可能的。

这可能是不可能的原因:

如果可以的话,怎么办呢? 如果不可能,为什么不呢? 即将到来的c ++ 17在这方面有什么改变吗?

更新:示例问题:通常,配置数据存储为字符串的层次结构或某种其他形式的“任何类型”。 然而,这导致丑陋的代码( config.get<int>("core.timeout") )并阻止编译器帮助解决,例如,打字错误( config.get<int>("core.timeuot") )。

通过使用其真实类型声明每个配置变量,编译器可以检查类型并防止拼写错误。 但是,需要使用自定义代码将配置数据读入正确的成员变量。 如果添加了新配置开关,则很容易忘记更新此代码。

只需指定所有成员的类型和名称,然后让编译器自动生成类(包括读取配置文件的方法)将会很方便。 这是我要求的功能的可能用例。

C ++没有反射工具(还)。 特别是,不可能以您希望的方式生成和操作实体名称。

然而,预处理器可以以有限的方式做到这一点,(在Boost.PP的帮助下,使得它能够打开它的样板)使我们能够编写以下内容(直接取自我的另一个答案 ):

#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0(...) \
     ((__VA_ARGS__)) GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_1

#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_1(...) \
     ((__VA_ARGS__)) GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0

#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0_END
#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_1_END

// Double the parentheses of a Boost.PP sequence
// I.e. (a, b)(c, d) becomes ((a, b))((c, d))
#define GLK_PP_SEQ_DOUBLE_PARENS(seq) \
    BOOST_PP_CAT(GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0 seq, _END)


#define MAKE_ONE_VARIABLE(r, data, elem) \
    BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem) = BOOST_PP_TUPLE_ELEM(2, elem);

#define MAKE_CLASS(className, members) \
    struct className { \
        BOOST_PP_SEQ_FOR_EACH(MAKE_ONE_VARIABLE, ~, GLK_PP_SEQ_DOUBLE_PARENS(members)) \
    }

......并按原样使用它:

MAKE_CLASS(Baz, (int, foo, 42)(float, bar, 0.1f));

...扩展到:

struct Baz {
    int foo = 42;
    float bar = 0.1f;
};

使用模板定义类成员的名称是不可能的,并且字符串文字很难与模板一起使用。 但是,如果您愿意使用类型作为成员标识符,则可以实现接近您想要的内容。

我建议你的member_type类型的以下定义:

// Class member
template<class type_t, class name_t, type_t default_value = type_t() >
struct member_type {
    using type = type_t;
    using name = name_t;
    type_t value = default_value;
};

然后,您将定义使用模板生成的成员的类型。 像这样的东西:

template<class ... T>
struct t_templated_members
{ 
    using t_members_list = std::tuple<T...>;
    t_members_list members;
};

并且您将以与您建议的方式类似的方式定义成员列表,但用类型替换成员的名称。

// "names" of your members
struct member_x {};
struct member_y {};

using foo = t_templated_members< 
    member_type<int, member_x, 10>,
    member_type<char, member_y, 'a'> >;

使用一些帮助程序模板,您可以根据成员的“名称”类型获取成员的值。

namespace details
{
    // Checks if the member at index I is the right one
    template<class T, class U, size_t I>
    using is_right_member = std::is_same<
        typename std::tuple_element_t<I, typename U::t_members_list>::name, T>;

    // Get the index of a member
    template<class T, class U, size_t I = 0 >
    struct find_element : public std::conditional_t<
        is_right_member<T, U, I>::value,
        std::integral_constant<decltype(I), I>,
        find_element<T, U, I + 1>> 
    { };
}

template<class T, class U>
auto & member_get(U & host)
{
    constexpr auto index = details::find_element<T, U>::value;

    return std::get<index>(host.members).value;
};

使用member_get您现在可以访问为foo定义的成员:

#include <iostream>

int main()
{
    foo my_foo;

    auto & x = member_get<member_x>(my_foo);
    std::cout << x << ' ';
    x = 6;
    std::cout << member_get<member_x>(my_foo) << '\n';

    std::cout << member_get<member_y>(my_foo) << ' ';
    member_get<member_y>(my_foo) = 'b';
    std::cout << member_get<member_y>(my_foo) << '\n';

    return 0;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM