get the number of fields in a class
Asked Answered
M

3

12

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.

Maugham answered 22/8, 2017 at 14:27 Comment(19)
What is the use case for this? Maybe a different solution could be had thereChafee
Nothing I know of that does not involve macro kung-fu. I suggest using std::tuple when you need to iterate or count fields.Crux
C++ doesn't work like that. Interestingly many such questions have popped up lately.Ovine
and yet something like magic_get exists - so there is a way...?Maugham
The way would be to use 'magic_get'. This is such a strange thing to do that I doubt it will enter into the standardChafee
What's the point in that?Jeth
@Jeth at the very least it might be viewed as an interesting exercise. In my case I want to validate that all fields of a class have been serialized or something like that - I want to enforce at compile time that something is done for all fields.Maugham
For that case, you could use a key/value hash (unordered) map instead of individual fields, and iterate over that to do whatever you need for each field.Chafee
@EyalK. not an option - this is planned for all my composite types in my project and would kill the performance and make the code uglier. I still think a subset of magic_get is what I'm looking for - I just hope it's a very small part of the library, but it's too complex for me to extract.Maugham
@Maugham you use wrong language or approach.Embolic
Even if you check that "something" is done for all fields, you still need a unit test to see that it is done correctly. Otherwise all serialization could be 0 0 0.Pyromorphite
@BoPersson In my case all I want to enforce is the use of a preprocessor identifier (that expands to nothing) at the start of each field definition. You might argue that this is nonsense but after quite a bit of thinking I've come to this point. If I explained my entire use case it would take a few pages of text. And there might be other use cases for this. Doesn't anyone like a challenge?!?!Maugham
@Maugham I cannot imagine a scenario where every class in a project would need serialization. A challenge is nice when there are practical applications, but this doesn't seem to be the caseChafee
Given that at compile time, you won't know which variables have been serialised or not; why not use assertions - that's what they're there for. On creation of your class, perform a fake serialisation, and compare to a static size member.Acknowledgment
@Maugham I think you'd get better answers if you post a question with the problem you're trying to solve, rather than post your solution and issues you're having with it.Acknowledgment
@EyalK. I'm trying to make a fully-reloadable engine where almost everything is in a separate dll. I already have an initial version of this where I can hotswap almost any part of the engine. Currently I'm rewriting the codegen/serialization. By serializing/deserializing fields/classes I can even support changing of the layout of objects at runtime - I can add a new field! But I'm done explaining myself - I thought this is a good-enough and direct Stack Overflow question - I guess it's not.Maugham
@Maugham It is a good question, and you got an answer, which is that currently this cannot be done using the standard. All other comments are trying to help you find ways to do what you need done in other ways that are possibleChafee
You may look at some library to add reflexivity to your class as boost hana and ADAPT_STRUCT or visit_struct.Rozamond
@Rozamond I looked at those and decided on a different approach - thanks for mentioning them. My main 2 reasons for not going that route is that they are very template heavy - if I indeed do this for almost every type in my codebase there would be a lot of bloat. The second reason is that I need some way to attach user defined attributes to fields - which I currently do with preprocessor identifiers that expand to nothing, but my parser takes them into accountMaugham
T
11

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.

Triforium answered 23/8, 2017 at 10:40 Comment(8)
Do you think it's possible to make it work with private fields as well? That makes a type not aggregate initializable but perhaps by friend-ing some of the constexpr functions or the struct trait classes...?Maugham
@onqtam: I doubt it, but - I'm not a crazy Russian genius, so who knows...Triforium
This will also not work for Derived; it's not an aggregate because it has a base class and thus cannot be aggregate-initialized.Tanguy
@Tanguy FWIW it is since C++17Tanta
excuse my ignorance but how do you call these functions?Croix
@Croix fields_count<Derived>()Edifice
@Makogan: I edited my answer to focus on the part you use (and also possibly to account for changes in magic_get?)Triforium
@LightnessRacesinOrbit I don't think this works for derived classes. The docs explicitly state it does not work. boost.org/doc/libs/develop/doc/html/boost_pfr/…Auk
C
4

You can't do that (out of the box) as there is no reflection in C++ (yet). You need to explore other options such as 3rd party libraries.

Cytochrome answered 22/8, 2017 at 14:30 Comment(5)
"You can't do that (out of the box) as there is no reflection in C++." - I am hoping that statement ends with a "yet", because, there are ongoing efforts to bring some form of it... Perhaps we'll see some in C++20?? C++23???Stringer
You can do that with TMP. Have a look at magic_get and structured binding.Tanguy
Ron: O RLY? Actually, you definitely can do that out of the box. And C++ does have reflection (it just doesn't know it yet...); see my answer.Triforium
@WhiZTiM: Try C++14.Triforium
@RustyX: You don't even need structured binding it seems.Triforium
M
4

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.

Maureenmaureene answered 23/7, 2019 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.