Is there a way to get the number of fields of a class?
struct Base {
char a;
int b;
};
struct Derived : Base {
std::string c;
};
static_assert(num_fields<Base>::value == 2);
static_assert(num_fields<Derived>::value == 1);
I've found this question but it is very dated - I am hoping something can be stitched together with C++14/17 (after all we now have something like magic_get - perhaps some subset from it...?)
EDIT: - a compiler hook would also work even if it's only for MSVC or GCC or Clang - I use all 3.
Indeed, Antony Polukhin has shown us that C++ does have (some) reflection, since C++14, without knowing it; and that you can extract information about the fields. ... well, at least for plain-old-data structs/classes. Watch his CppCon 2016 talk:
C++14 Reflections Without Macros, Markup nor External Tooling / Antony Polukhin
And then you use:
template <class T>
constexpr std::size_t fields_count() noexcept;
which gets you the field count. To use that you need these two file:
https://github.com/apolukhin/magic_get/blob/develop/include/boost/pfr/detail/config.hpp
https://github.com/apolukhin/magic_get/blob/develop/include/boost/pfr/detail/fields_count.hpp
and that should be enough.
Here's the modified version of einpoklum's answer to use at compile time:
template <size_t I>
struct ubiq_constructor
{
template <typename Type>
constexpr operator Type&() const noexcept
{
return Type(*this);
}
};
template <size_t fields, class POD>
struct fields_count
{
constexpr static size_t count = fields;
typedef POD type;
};
// why use size_t& in the constexpr function in the first place?
template <class T, size_t I0, size_t ... I>
constexpr auto fields_total(const std::index_sequence<I0, I...>&)
-> fields_count<sizeof...(I) + 1, decltype(T{ ubiq_constructor<I0>(), ubiq_constructor<I>()...})>
{
return fields_count<sizeof...(I) + 1, decltype(T{ ubiq_constructor<I0>(), ubiq_constructor<I>()... })>();
}
template <class T, size_t ... I>
constexpr auto fields_total(const std::index_sequence<I...>&)
{
return fields_total<T>(std::make_index_sequence<sizeof...(I) - 1>());
}
//use this for convinience to return number of fields at compile time
template <class T>
constexpr size_t fields_total(const T&)
{
auto counted = fields_total<T>(std::make_index_sequence<sizeof(T) / sizeof(char)>());
return decltype(counted)::count;
}
Also, the approach for getting types of fileds mentioned in the video from CppCon 2016 seems to me rather difficult, and as I have understood, depends on BOOST library.
UPADTE I thought of a less cumbersome way, which would be to base the implementation on existing type_traits functions. Unfortunately, std::is_constructible_v is not an option here, since it resolves the resulting type via "()" constructors, not designated initialization "{}". So, after some modification of is_constructible implementation, I came up with a more elegant solution using SFINAE.
//to generate index sequence
template <size_t sz>
struct iseq_type
{
using indx_seq = decltype(std::make_index_sequence<sz>()) ;
};
template <class POD, class types_map = pod_map /*tuple of types to deduce from*/, class divisor = char, size_t predict = sizeof(POD) / sizeof(divisor) + 1>
class refl_traits
{
template <size_t I>
struct ubiq_constructor
{
template <typename Other>
constexpr operator Other&() const noexcept
{
return Other(*this);
}
};
template <class allowed>
struct ubiq_explicit
{
template <class other>
constexpr operator other&() = delete;
constexpr operator allowed&() noexcept;
};
template <class, class ... POD /*and index sequence*/>
struct args_allowed_ : public std::false_type
{};
template <class POD, size_t ... indx>
struct args_allowed_ < std::void_t<decltype(POD{ ubiq_constructor<indx>() ... }) > , POD, std::index_sequence<indx... >> : public std::true_type
{};
template <class POD, class T, size_t ... indx>
struct args_allowed_ < std::void_t<decltype(POD{ ubiq_constructor<indx>() ..., ubiq_explicit<T>() }) > , POD, T, std::index_sequence<indx... >> : public std::true_type
{};
template <size_t map_iter = 0, class ... prev_args>
constexpr static auto get_types()
{
static_assert(map_iter < std::tuple_size<types_map>::value, "Provided map could not deduce argument №");
if constexpr (sizeof...(prev_args) == fields_count())
return std::tuple<prev_args...>();
else if constexpr (is_valid_arg<std::tuple_element_t<map_iter, types_map>, sizeof...(prev_args)>::value)
return get_types<0, prev_args..., std::tuple_element_t<map_iter, types_map>>();
else return get_types<map_iter + 1, prev_args...>();
}
public:
template <size_t pred_start = predict>
constexpr static size_t fields_count()
{
static_assert(std::is_aggregate_v<POD>, "Provided class can not be aggregate initialized!");
if constexpr (!args_allowed<pred_start>::value)
return fields_count<pred_start - 1>();
else return pred_start;
}
//get maximum number of args for designated initialization
template <size_t predict_>
using args_allowed = args_allowed_<std::void_t<>, POD, typename iseq_type<predict_>::indx_seq>;
//check if the arg_num argument is of type T
template <class T, size_t arg_num>
using is_valid_arg = args_allowed_<std::void_t<>, POD, T, typename iseq_type<arg_num>::indx_seq>;
using field_types = decltype(get_types());
//.....
};
I've created a repo and moved the example code there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With