简体   繁体   中英

Is there a way to iterate over an enum without knowing its size in c++

Hello I have the following enum

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

what I want to do is try to doing something like this pseudocode:

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

Is there anyway to achieve this?

Edit: I do not have the control over enum. This enum is provided by a different file from a 3rd part library. I can probably copy it but not change the original enum. I want to write the members of the enum library to a different file.

No. At least not directly. Enums are actually not a set of constants. Rather they are a type that comes with a set of named constants. Difference is: for example 42 is a completely valid value of params_Solver , it just has no name.

A common way to enable iteration is to add a sentinel value:

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

And then iterate from 0 till num_params_Solver . The nice thing is that you can add another constant and num_params_Solver will still be correct. The limitation is that it only works for enums without custom values.

Common way

You could add an item at the end of your enumeration this way:

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

And loop over it with:

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

This solution does not work if you assign values to your enum members other than the default ones though.

Other way

You can use a regular array without having to add a last element. However the syntax is quite redundant as you have to specify yourself the members of the enum:

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

You can then iterate over the enum using:

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

All values representable in the underlying type of the enum are valid values of the enum, whether they are given a name or not, and iterating over a 64-bit enum range might take longer than you'd care to wait... :)

Another complication, if you mean to iterate over just the named enumerators, that's possible, but takes more work, and some considerations. As already stated, if you do not give any custom values to your enumerators, then they will start at zero and increment. However, you can put gaps in the numbers, you can jump backwards, you can have repeats. Do two enumerators with the same value count as one iteration or one per name? Different situations will have different answers.

If you do this without custom values for your enumerators, you can put an extra "dummy" value at the end of the list and treat it as the count of the enumerators. But it will be wrong if you have gaps, duplicates, or start at a value other than 0. It also can fail if someone adds new enumerator values after the dummy value.

There are some 3rd party libraries that can help with this. If you don't mind a bit of extra code, the "Better Enums" open source library is very useful header-only library. https://github.com/aantron/better-enums It gives meta data over your enums with a nice syntax, allowing iterators, ranged for loop use, converting to/from string names and some more.

C++ does not provide intrinsic support for what you are trying to do.

You can add a some boilerplate to accomplish what you want, so that the code that is using the range of the enum is not aware of the enums. In the code example, that knowledge is encapsulated in the params_Solver_Range helper class.

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

No. The reflection proposal, which does what you want, may arrive in [c++23].

Without it, you can copy paste the enum , possibly quoting it, into an array, and iterating over the copy.

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

then just do a for loop over that.

for (char const* const& enum_member: members)
{
  auto index = static_cast<long long unsigned>(&enum_member-members);
  printf("%llu, %s\n", index, enum_member);
}

Live example .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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