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.

2
  • Why do you want to know how many without knowing what they are? Commented Apr 7, 2010 at 3:51
  • I know how to work with types, I am not familiar with other aspects and features of template meta-programming such as this Commented Apr 7, 2010 at 3:53

5 Answers 5

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++.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
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.

Comments

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.

1 Comment

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.
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>());

2 Comments

Can't aggregates have bitfields? The upper bound might need to be higher.
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.
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;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.