在C++中,是否有一种不知道枚举大小的情况下迭代枚举的方法?

3

你好,我有以下枚举类型

enum params_Solver {
  params_Solver_Lorem,
  params_Solver_Ipsum,
  params_Solver_Simply,
  params_Solver_Dummy,
  params_Solver_Test,
  params_Solver_Typesetting,
  params_Solver_Industry,
  params_Solver_Scrambled
};

我想做的是尝试执行类似于这样的伪代码:

for (auto enum_member: params_Solver)
{
    print(index, enum_member); // output looks like this: "0, params_Solver_Lorem", "1, params_Solver_Ipsum" etc
}

有没有办法实现这个?

编辑: 我不能控制枚举。 这个枚举是由第三方库的不同文件提供的。 我可能可以复制它但不能更改原始枚举。 我想将枚举库中的成员写入另一个文件。


1
枚举类型没有“大小”概念。您可以将枚举看作是一系列常量变量的枚举:constexprt auto params_Solver_Lorem = 0; constxpr auto params_Solver_Ipsum = 1; ... 没有办法以可枚举的方式获取这些变量。 - JulianW
3
传统方法是:params_Solver_Last // 不要移动,保持在最后和一个for循环。 - Jeffrey
你也无法检索名称。在C++中还没有反射。 - Jarod42
这有帮助吗?根据这个答案,假设没有间隙,你仍然需要知道第一个和最后一个值。 - Maaz
正如所示,这听起来像是一个 XY 问题。如果枚举值是非连续的,你能提供一个更现实的用例吗? - G.M.
2
这个回答解决了你的问题吗?如何迭代枚举? - acraig5075
5个回答

5

不,至少不是直接的。枚举实际上不是一组常量,而是一种带有一组命名常量的类型。区别在于:例如42params_Solver的完全有效值,它只是没有名称。

启用迭代的常见方法是添加一个哨兵值:

enum params_Solver {
  params_Solver_Lorem,
  params_Solver_Ipsum,
  params_Solver_Simply,
  params_Solver_Dummy,
  params_Solver_Test,
  params_Solver_Typesetting,
  params_Solver_Industry,
  params_Solver_Scrambled,
  num_params_Solver          // <----
};

0开始迭代到num_params_Solver。好的一点是,你可以添加另一个常量,num_params_Solver仍然是正确的。限制在于它只适用于没有自定义值的枚举。


3

常用方法

您可以通过以下方式,在枚举末尾添加一个项:

enum params_Solver {
  params_Solver_Lorem,
  params_Solver_Ipsum,
  params_Solver_Simply,
  params_Solver_Dummy,
  params_Solver_Test,
  params_Solver_Typesetting,
  params_Solver_Industry,
  params_Solver_Scrambled,
  Last
};

使用以下方式循环它:

for (int i = params_Solver_Lorem; i != Last; i++) {
    // some code
}

如果你给枚举成员分配了除默认值以外的值,则此解决方案无效。

另一种方法

您可以使用常规数组,而无需添加最后一个元素。但是,语法非常冗余,因为您必须自己指定枚举的成员:

constexpr params_Solver members[] {
    params_Solver_Lorem,
    params_Solver_Ipsum,
    params_Solver_Simply,
    params_Solver_Dummy,
    params_Solver_Test,
    params_Solver_Typesetting,
    params_Solver_Industry,
    params_Solver_Scrambled 
};

您可以使用以下方式迭代枚举:

for (auto m: members) {
    // Some code
}

为什么要使用std::initializer_list?为什么不只使用数组? - eerorika
其实我不明白为什么我要使用initializer_list,数组也可以。 - rubytox
如果我使用std::array,我仍然需要指定元素的数量。我可以使用std::vector,但我不确定这是否是正确的做法。 - rubytox
1
你不需要使用 std::array,并且你绝对不应该为此使用 std::vector。相反,你可以使用一个普通的数组:constexpr params_Solver members[] {...};。借助于C++17的类型推导指南,你可以使用std::array,甚至不需要指定元素类型:constexpr std::array members {...};。在C++17之前,也可以通过 constexpr auto members = make_array(...); 来解决,但这要求你要有一个聪明的 make_array实现,这个实现曾被建议被加入TS中进行标准化,但由于类型推导指南而被取消了。 - eerorika
哦,我明白了。非常感谢,我会更新我的答案。 - rubytox

1
C++不提供对您正在尝试完成的内容的内在支持。
您可以添加一些样板代码来实现您想要的功能,以便使用枚举范围的代码不了解枚举。在代码示例中,该知识被封装在params_Solver_Range辅助类中。
#include <iostream>
#include <stdexcept>
#include <utility>

using std::ostream;
using std::cout;
using std::underlying_type_t;
using std::logic_error;

namespace {

enum class params_Solver {
    Lorem,
    Ipsum,
    Simply,
    Dummy,
    Test,
    Typesetting,
    Industry,
    Scrambled
};

auto operator<<(ostream& out, params_Solver e) -> ostream& {
#define CASE(x) case params_Solver::x: return out << #x
    switch(e) {
        CASE(Lorem);
        CASE(Ipsum);
        CASE(Simply);
        CASE(Dummy);
        CASE(Test);
        CASE(Typesetting);
        CASE(Industry);
        CASE(Scrambled);
    }
#undef CASE

    throw logic_error("unknown params_Solver");
}

auto operator+(params_Solver e) {
    return static_cast<underlying_type_t<decltype(e)>>(e);
}

auto operator++(params_Solver& e) -> params_Solver& {
    if (e == params_Solver::Scrambled) throw logic_error("increment params_Solver");
    e = static_cast<params_Solver>(+e + 1);
    return e;
}

class params_Solver_Range {
    bool done = false;
    params_Solver iter = params_Solver::Lorem;

public:
    auto begin() const -> params_Solver_Range const& { return *this; }
    auto end() const -> params_Solver_Range const& { return *this; }
    auto operator*() const -> params_Solver { return iter; }
    bool operator!=(params_Solver_Range const&) const { return !done; }
    void operator++() {
        if (done) throw logic_error("increment past end");
        if (iter == params_Solver::Scrambled) done = true;
        else ++iter;
    }
};

} // anon

int main() {
    for (auto e : params_Solver_Range()) {
        cout << +e << ", " << e << "\n";
    }
}

1

不行。反射提案可以实现您想要的功能,可能会在[c++23]中推出。

如果没有它,您可以将enum复制粘贴到数组中,并引用它,然后遍历副本。

constexpr char const* members[] {
  "params_Solver_Lorem",
  "params_Solver_Ipsum",
  //etc
};

然后只需对此进行for循环。
for (char const* const& enum_member: members)
{
  auto index = static_cast<long long unsigned>(&enum_member-members);
  printf("%llu, %s\n", index, enum_member);
}

实时例子.


1
所有可表示在枚举底层类型中的值都是枚举的有效值,无论它们是否有名称,并且在64位枚举范围内迭代可能需要比您想等待的时间更长... :)
另一个复杂问题是,如果您想要仅迭代命名的枚举器,则可以实现,但需要更多的工作和一些考虑。如前所述,如果您没有为枚举器提供任何自定义值,则它们将从零开始递增。但是,您可以在数字中放置间隔,可以向后跳转,可以重复。两个具有相同值的枚举器是否计为一个迭代或每个名称计算一个?不同情况将有不同的答案。
如果您在没有枚举器的自定义值的情况下执行此操作,则可以在列表末尾放置额外的“虚拟”值,并将其视为枚举器的计数。但是,如果存在间隙、重复项或从值0以外的值开始,则会出现错误。如果有人在虚拟值之后添加新的枚举器值,它也可能失败。

有一些第三方库可以帮助解决这个问题。如果您不介意写一些额外的代码,"Better Enums"开源库是一个非常有用的头文件库。https://github.com/aantron/better-enums 它使用漂亮的语法为枚举提供元数据,允许迭代器、范围循环使用,将枚举转换为/从字符串名称等等。


谢谢!实际上我正在从一个不在我的控制下的不同文件中读取枚举。我基本上想将枚举的值写入另一个文件中。 - Morpheus
@Morpheus 你是什么意思?枚举的表示是一个整数数据(通常是int),并作为此类序列化。你的文件是什么格式? - Swift - Friday Pie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接