【问题标题】:Class member variables based on variadic template基于可变参数模板的类成员变量
【发布时间】:2017-10-18 10:04:15
【问题描述】:

给定一个类型、名称和默认值的列表,我可以很容易地编写一个工具来生成有效的 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>>;

这可能的原因:

不可能的原因:

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

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

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

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

【问题讨论】:

  • 您是在问最新版本的 c++ 是否有可能,还是在问是否有可能在理论上的未来版本的 c++ 中做到这一点?
  • ""一切"都应该是可能的。" 任何数学运算都可以用一个完整的巡演系统来表达。这并不意味着一切皆有可能。我想您可以为一种新语言编写一个编译器,该编译器支持您使用模板元编程实现的功能。
  • 可能是这样的:std::tuple&lt;typename MemberTypes::type...&gt; all_members = {MemberTypes::default...};
  • 你通过写下它的定义来创建一个类。没有其他办法。您不能获取一段编译时数据并将其转换为具有模板魔法的类。您需要源代码中存在语法和语义上有效的类定义。为什么需要生成类?为什么std::tuple 不是可行的替代品?
  • 关于 "member_type&lt;float, "bar", 0.1f&gt;"float c++ 不支持模板参数。有关允许的非类型模板参数类型的列表,请参阅here

标签: c++ templates c++14 template-meta-programming c++17


【解决方案1】:

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

然而,预处理器可以以有限的方式做到这一点,这(在 Boost.PP 的帮助下,使其打勾的样板文件)使我们能够编写以下内容(直接取自 another answer of mine):

#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;
};

【讨论】:

  • 这并不意味着反对答案,而是补充。在这一点上,我会考虑使用/编写一些(小)外部工具来生成代码作为自定义包含。当然可以编写一个更具可读性、可维护性和功能性的生成器。以两阶段构建和动态依赖为代价,但可能是值得的。另一方面, boost 程序选项使用几乎相同的语法...
  • @luk32 绝对。基于 Boost.PP 的代码生成的一个优点是它可以使用已经提供的工具进行操作,但是如果您可以修改您的工具链,则可以做出选择:)
【解决方案2】:

使用模板定义类成员的名称是不可能的,并且字符串文字与模板一起使用具有挑战性。但是,如果您愿意将类型用作成员标识符,则可以实现接近您想要的结果。

我建议为您的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;
}

【讨论】:

  • 这是一个非常有趣的方法。我对这仅适用于整数类型是对的吗?
  • @Drag-On 不幸的是,c++ 对非类型模板参数的类型进行了限制。有关允许的类型列表,请参阅 here。最有趣的可能是整数类型、指针和枚举。但是,如果您放弃对默认值的要求,您可以使用您想要的任何类型,只要它是默认可构造的。编辑:实际上,您可以检测T 是否与非类型参数不兼容,并提供不接受默认值的替代member_type
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-20
  • 2012-04-28
  • 1970-01-01
相关资源
最近更新 更多