Reputation: 51505
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.
Upvotes: 8
Views: 1692
Reputation: 49
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;
}
Upvotes: 0
Reputation: 459
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>());
Upvotes: 1
Reputation: 137860
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++.
Upvotes: 8
Reputation: 490328
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.
Upvotes: 1
Reputation: 95569
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.
Upvotes: 1