简体   繁体   English

C++20 遍历模板参数

[英]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++.这在当前标准 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.这将需要通过可以解析和修改 C++ 源代码的工具进行外部预处理。

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 .您唯一可以做的就是在std::underlying_typestd::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 .此外,在编译时执行此操作比简单的循环更棘手,并且对于大于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.) v()可以替换为v.value 。或者只要plant不使用占位符作为非类型模板参数的类型,也可以只是v 。)

As others have stated, C++ does not have reflection and there is no 'generic, for all cases' way to do it.正如其他人所说,C++ 没有反射,也没有“通用的,适用于所有情况”的方法来做到这一点。

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.然后递归调用该函数相同的函数,但模板化参数是递增的枚举值,作为 int,并转换回枚举类型。 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.您还必须将 COUNT 保留在最后 - 否则会有您错过的值。

Courtesy of Boost.Describe, C++ does have a workable reflection system.由 Boost.Describe 提供,C++ 确实有一个可行的反射系统。

#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 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参考: https ://www.boost.org/doc/libs/1_81_0/libs/describe/doc/html/describe.html#examples

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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