简体   繁体   中英

C++20 loop through template parameters

I have a set of classes which often have a member function which take an enum as template parameter. I would like to loop over these. I'm trying to build a solution that would automatically loop over them.

struct fruit
{
  enum Value : uint8_t
  {
    apple,
    banana,
    kiwi,
  };

  fruit() = default;
  fruit(Value afruit) : value(afruit) { }
  Value value;
};

struct field
{
    template<fruit f> 
    void plant()
    {
    }
};

struct barn
{
    template<fruit f> 
    void store(int somearg)
    {
    }
};

struct fruit_loop
{
//?
};

int main(int argc, char** argv)
{

    auto f = field{};

    // replace with something like a for loop ?
    f.plant<fruit::apple>();
    f.plant<fruit::banana>();
    f.plant<fruit::kiwi>();


    return 0;
}

This is not possible in current standard C++. There is no way to decide which values of an enumeration type have a named enumerator. (Though there are some tricks based on compiler extensions or specific compiler behavior to get some form of reflection.)

This would require an external preprocessing by a tool that can parse and modify C++ sources.

The only thing you can do is to loop over all values of the underlying type, assuming that it is fixed, with the help of std::underlying_type and std::numeric_limits . This will however include (valid) enumeration values outside those with a named enumerator as well. Also, doing this at compile-time is a bit more tricky than a simple loop and will be unfeasible for underlying types larger than uint8_t .

You can write a function such as

template<auto V>
constexpr inline auto constant = std::integral_constant<decltype(V), V>{};

constexpr void for_all_fruit(auto&& f) {
    f(constant<fruit::apple>);
    f(constant<fruit::banana>);
    f(constant<fruit::kiwi>);
}

and then call the function wherever you need to apply something to every value:

auto f = field{};

for_all_fruit([&](auto v){ f.plant<v()>(); });

( v() may be replaced by v.value . Or also just v as long as plant doesn't use a placeholder as type of the non-type template parameter.)

As others have stated, C++ does not have reflection and there is no 'generic, for all cases' way to do it.

If you're willing to accept some restrictions, and can follow a convention, you can do something like this:

struct fruit
{
  enum Value : uint8_t
  {
    apple,  //All elements
    banana, //must be 
    kiwi,   //continuous, with no gaps

    count //MUST BE LAST element in enum
  };
//Rest of your code...
}

template<fruit::Value v>
void plantAll(field f)
{
    f.plant<v>();
    plantAll<(fruit::Value)(v+1)>(f);
}

template<>
void plantAll<fruit::Value::count>(field f)
{
    
}


int main()
{
    auto f = field{};
    plantAll<fruit::apple>(f);
}

How does this work? We create a templated function, that recursively calls itself. We also add a 'count' element in the end of the enumeration.

In the templated function, you do your work with the given Value. Then recursively call the function same function, but the templated argument is the incremented enum value, as an int, and cast back to the enumeration type. You create a specialization for the 'count' type that does nothing, that ends the recursion.

You have to keep all values in the enum continuous - otherwise you will get values without a named enumerator (which I you don't want). You also have to keep COUNT last - otherwise there will be values that you missed.

Courtesy of Boost.Describe, C++ does have a workable reflection system.

#include <boost/describe.hpp>

BOOST_DEFINE_ENUM_CLASS(fruit, apple, banana, kiwi)

struct field
{
    template<fruit f>
    void plant() {}
};

int
main()
{
    using boost::mp11::mp_for_each;
    using boost::describe::describe_enumerators;

    auto f = field();

    mp_for_each< describe_enumerators<fruit> >([&](auto D) {

        f.plant<D.value>();

    });
}

https://godbolt.org/z/ns7zfsoxz

We can go further:

#include <boost/describe.hpp>
#include <iostream>

BOOST_DEFINE_ENUM_CLASS(fruit, apple, banana, kiwi)

template<class E> char const * enum_to_string( E e )
{
    using boost::mp11::mp_for_each;
    using boost::describe::describe_enumerators;

    char const * r = "(unnamed)";

    mp_for_each< describe_enumerators<E> >([&](auto D) {

        if( e == D.value ) 
            r = D.name;

    });

    return r;
}
struct field
{
    template<fruit f>
    void plant() {

        std::cout << "planted: " << enum_to_string(f) << '\n';

    }
};

int
main()
{
    using boost::mp11::mp_for_each;
    using boost::describe::describe_enumerators;

    auto f = field();

    mp_for_each< describe_enumerators<fruit> >([&](auto D) {

        std::cout << "planting: " << D.name << '\n';
        
        f.plant<D.value>();

    });
}

expected output:

planting: apple
planted: apple
planting: banana
planted: banana
planting: kiwi
planted: kiwi

Reference: https://www.boost.org/doc/libs/1_81_0/libs/describe/doc/html/describe.html#examples

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM