【问题标题】:C++ template meta-programming, number of member variables?C ++模板元编程,成员变量的数量?
【发布时间】:2025-12-28 08:15:06
【问题描述】:

是否可以在 C++ 中确定泛型类中变量/字段的数量? 例如

// suppose I need metaclass number_members determines number of members

struct example { int i, j; };
assert(number_members<example>::value==2);

我查看了 mpl 但找不到实现。

谢谢。

【问题讨论】:

  • 为什么你想知道有多少却不知道它们是什么?
  • 我知道如何使用类型,我不熟悉模板元编程的其他方面和特性,例如这个

标签: c++ templates metaprogramming


【解决方案1】:

没有。 C++ 不提供对结构的一般内省。

你可以试试C++0x std::tuple,它具有一般PODstruct的一些功能。或者,尝试从Boost MPL 库中推出您自己的。如果您刚刚开始使用 C++,那会有点高级。

【讨论】:

  • 同时查看 Boost.Fusion。这是混合模板和运行时代码的好方法。当我需要 simili-reflection 时,我个人使用 boost::fusion::map 作为 struct / class 的骨架。
【解决方案2】:

没有。不幸的是,C++ 没有内置的这种内省。但是,通过一些额外的预处理,例如QtMeta Object Compiler (moc),您可以实现类似的效果……QMetaObject 类提供了propertyCount();但是,您的类需要从 QObject 继承,使用 Q_OBJECT 宏,并注册所有这些属性才能正常工作......所以,简而言之,它不是自动的。

【讨论】:

    【解决方案3】:

    你不能直接这样做。那么显而易见的问题就是你想要完成的事情——你可能可以做你需要做的事情,但做这件事的方式可能会大不相同。

    【讨论】:

    • 实际上,并没有真正尝试做任何具体的事情。只是想我该怎么做,意识到我不知道怎么做,决定问一个问题。只是想学习其他方面。
    【解决方案4】:

    是的,有限制。你可以在mattkretz/virtools 找到我的实现。它需要 C++20,因为它使用概念。原则上,您可以使用enable_if 重写它,从而使其适用于 C++17。 Live example.

    这里的主要思想是将可检查的类型集限制为在单个基类中或仅在派生类中具有非静态数据成员的聚合(但仍允许空基类)。这与结构化绑定所施加的限制相同。然后你知道一个类型 T 是否有例如如果 T{anything_but_base_of&lt;T&gt;(), anything_but_base_of&lt;T&gt;(), anything_but_base_of&lt;T&gt;()} 是一个有效的表达式(即没有替换失败),则 3 个(或更多)成员。其中anything_but_base_of 是:

    template <class Struct> struct anything_but_base_of {
      template <class T>
      requires(!std::is_base_of_v<T, Struct>)
      operator T();
    };
    

    由于聚合初始化允许指定的初始化器数量少于聚合的成员数,因此必须从上限开始进行测试,然后向下递归到 0,直到找到可能的聚合初始化。我的实现使用的解构测试实际上不是 SFINAE 条件,而是产生了一个硬错误。因此,您也可以删除该代码,进行实现:

    namespace detail {
    template <class Struct> struct any_empty_base_of {
      template <class T>
      requires(std::is_base_of_v<T, Struct> && std::is_empty_v<T>)
      operator T();
    };
    
    template <class T, size_t... Indexes>
    concept brace_constructible =
       requires { T{((void)Indexes, anything_but_base_of<T>())...}; } 
    || requires { T{any_empty_base_of<T>(), ((void)Indexes, anything_but_base_of<T>())...}; }
    || requires { T{any_empty_base_of<T>(), any_empty_base_of<T>(), ((void)Indexes, anything_but_base_of<T>())...}; }
    || requires { T{any_empty_base_of<T>(), any_empty_base_of<T>(), any_empty_base_of<T>(), ((void)Indexes, anything_but_base_of<T>())...}; };
    
    template <class T, size_t... Indexes>
    requires brace_constructible<T, Indexes...>
    constexpr size_t struct_size(std::index_sequence<Indexes...>)
    {
      return sizeof...(Indexes);
    }
    
    template <class T>
    requires requires { T{}; }
    constexpr size_t struct_size(std::index_sequence<>)
    {
      static_assert(std::is_empty_v<T>,
                    "Increase MaxSize on your struct_size call. (Or you found a bug)");
      return 0;
    }
    
    template <class T, size_t I0, size_t... Indexes>
    requires(!brace_constructible<T, I0, Indexes...>)
    constexpr size_t struct_size(std::index_sequence<I0, Indexes...>)
    {
      // recurse with one less initializer
      return struct_size<T>(std::index_sequence<Indexes...>());
    }
    } // namespace detail
    

    最后,我们需要一个合理的上限作为起点。正确的上限是sizeof(T) * CHAR_BIT,对于每个非静态数据成员占用一个位并且整个结构不包含填充的位域的情况。考虑到使用正确上限的编译时间成本,我选择了一个更明智的启发式方法,即 sizeof(T)

    template <typename T, size_t MaxSize = sizeof(T)>
    constexpr inline std::size_t struct_size =
        detail::struct_size<T>(std::make_index_sequence<MaxSize>());
    

    【讨论】:

    • 聚合不能有位域吗?上限可能需要更高。
    • 你是对的,谢谢你的提示。使用小于CHAR_BIT 的位域会破坏我使用的启发式方法。 sizeof(T) * CHAR_BIT 将是正确的上限。我会将其添加到vir::struct_size 的文档中并相应地修改我的答案。