簡體   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