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;
}
CodePudding user response:
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.)
CodePudding user response:
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.