繁体   English   中英

C++20 遍历模板参数

[英]C++20 loop through template parameters

我有一组类,它们通常有一个成员函数,该成员函数将枚举作为模板参数。 我想遍历这些。 我正在尝试构建一个可以自动循环遍历它们的解决方案。

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;
}

这在当前标准 C++ 中是不可能的。 无法确定枚举类型的哪些值具有命名枚举器。 (虽然有一些基于编译器扩展或特定编译器行为的技巧来获得某种形式的反射。)

这将需要通过可以解析和修改 C++ 源代码的工具进行外部预处理。

您唯一可以做的就是在std::underlying_typestd::numeric_limits的帮助下循环遍历基础类型的所有值,假设它是固定的。 但是,这也将包括具有命名枚举器的枚举值之外的(有效)枚举值。 此外,在编译时执行此操作比简单的循环更棘手,并且对于大于uint8_t的基础类型是不可行的。

您可以编写一个函数,例如

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>);
}

然后在需要对每个值应用某些内容的任何地方调用该函数:

auto f = field{};

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

v()可以替换为v.value 。或者只要plant不使用占位符作为非类型模板参数的类型,也可以只是v 。)

正如其他人所说,C++ 没有反射,也没有“通用的,适用于所有情况”的方法来做到这一点。

如果你愿意接受一些限制,并且可以遵循约定,你可以这样做:

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);
}

这是如何运作的? 我们创建了一个模板函数,它递归地调用自己。 我们还在枚举的末尾添加了一个“计数”元素。

在模板函数中,您使用给定的值进行工作。 然后递归调用该函数相同的函数,但模板化参数是递增的枚举值,作为 int,并转换回枚举类型。 您为什么都不做的“计数”类型创建了一个特化,它结束了递归。

您必须使枚举中的所有值保持连续 - 否则您将获得没有命名枚举器的值(我不想要)。 您还必须将 COUNT 保留在最后 - 否则会有您错过的值。

由 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

我们可以更进一步:

#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>();

    });
}

预期输出:

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

参考: 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