Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

get the number of fields in a class

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.

like image 845
onqtam Avatar asked Aug 22 '17 14:08

onqtam


2 Answers

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.

like image 83
einpoklum Avatar answered Oct 26 '22 10:10

einpoklum


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.

like image 38
Sergey Kolesnik Avatar answered Oct 26 '22 09:10

Sergey Kolesnik