C++ template meta-programming, number of member variables?
Asked Answered
M

5

8

Is it possible in C++ to determine number of variables/fields in the generic class? for example

// suppose I need metaclass number_members determines number of members

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

I looked through mpl but could not find implementation.

thanks.

Macrae answered 7/4, 2010 at 3:41 Comment(2)
Why do you want to know how many without knowing what they are?Supereminent
I know how to work with types, I am not familiar with other aspects and features of template meta-programming such as thisMacrae
S
8

No. C++ does not provide general introspection into structures.

You can try a C++0x std::tuple, which has some of the features of a general POD struct. Or, try to roll your own from the Boost MPL library. That would be a bit advanced if you're just getting started with C++.

Supereminent answered 7/4, 2010 at 3:48 Comment(1)
Also look at Boost.Fusion while you are at it. It's a good way to mix templates and runtime code. I have personally use boost::fusion::map as a skeleton for struct / class when I needed simili-reflection.Morena
L
1

No. Unfortunately, C++ does not have that kind of introspection builtin. However, with some additional preprocessing such as Qt's Meta Object Compiler (moc), you can achieve something similar... the QMetaObject class provides a propertyCount(); however, your class would need to inherit from QObject, use the Q_OBJECT macro, and register the properties for all that to work... so, in short, it's not automatic.

Levee answered 7/4, 2010 at 3:55 Comment(0)
F
1

You can't do that directly. The obvious question then, is what you're trying to accomplish -- chances are that you can do what you need to, but the way to do it may be rather different.

Faretheewell answered 7/4, 2010 at 3:58 Comment(1)
actually, not really trying to do anything specific. Just thought how I could do it and realizing I do not know how, decided to ask a question. just trying to learn additional aspects.Macrae
A
1

Yes, with restrictions. You can find my implementation at mattkretz/virtools. It requires C++20, because it uses concepts. In principle, you can rewrite it using enable_if and thus make it work with C++17. Live example.

The main idea here is to restrict the set of types that can be inspected to aggregates with non-static data members either in a single base class or only in the derived class (but still allowing empty base classes). This is the same restriction structured bindings imposes. Then you know whether a type T has e.g. 3 (or more) members if T{anything_but_base_of<T>(), anything_but_base_of<T>(), anything_but_base_of<T>()} is a valid expression (i.e. no substitution failure). Where anything_but_base_of is:

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

Since aggregate initialization allows to specify fewer initializers than the aggregate has members one has to test starting from an upper bound and then recurse down to 0 until a possible aggregate initialization is found. The destructuring test my implementation uses is actually not a SFINAE condition, but rather produces a hard error. So you can just as well remove that code, making the implementation:

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

Finally, we need a sensible upper bound to start from. The correct upper bound would be sizeof(T) * CHAR_BIT, for the case of a bitfield where each non-static data member occupies a single bit and the whole struct contains no padding. Considering the compile time cost of using the correct upper bound, I settled on a more sensible heuristic of simply 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>());
Austrasia answered 11/2, 2020 at 16:5 Comment(2)
Can't aggregates have bitfields? The upper bound might need to be higher.Creek
You're right, thanks for the hint. The use of bitfields smaller than CHAR_BIT would break the heuristic I used. sizeof(T) * CHAR_BIT would be the correct upper bound. I will add it to the documentation of vir::struct_size and amend my answer accordingly.Austrasia
T
0

Yes, it's indeed possible. By employing a couple of metaprogramming techniques, we can determine the number of members in an aggregate type. The following implementation was made for C++17, but it's adaptable for C++14 or even C++11 with minor adjustments:

Example:

#include <type_traits>
#include <utility>

#include <iostream>

namespace impl {
    struct any_type {
        template<typename T>
        constexpr operator T();
    };

    template <typename T, typename... Ts>
    decltype(void(T{ std::declval<Ts>()... }), std::true_type{}) test_is_braces_constructible(std::size_t);

    template <typename, typename...>
    std::false_type test_is_braces_constructible(...);

    constexpr std::size_t max_depth = 128; // Define maximum recursion depth
    constexpr std::size_t max_depth_overflow = std::size_t(-1); // Define value for overflow

    template<std::size_t N, typename T, typename... Ts>
    struct count_aggregate_members : std::conditional_t<
        (N - 1 == max_depth) ? false : decltype(test_is_braces_constructible<T, Ts...>(0))::value,
        count_aggregate_members<N + 1, T, any_type, Ts...>,
        std::integral_constant<std::size_t, (N - 1 == max_depth ? max_depth_overflow : N)>
    > {};
}

template<typename T, typename = std::enable_if_t<std::is_aggregate_v<T>>>
using count_aggregate_members_t = typename impl::count_aggregate_members<0, T, impl::any_type>;

template<typename T>
constexpr auto count_aggregate_members_v = count_aggregate_members_t<T>::value;

struct foo {
    int i;
    float f;
    double d;
};

int main() {
    std::cout << count_aggregate_members_v<foo>; // Output: 3

    return 0;
}
Transformation answered 2/5 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.