3

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.

Morpheus
  • 3,285
  • 4
  • 27
  • 57
  • 1
    Enums do not have a "size". You can think of an enum like a enumeration of constant variables: `constexprt auto params_Solver_Lorem = 0;` `constxpr auto params_Solver_Ipsum = 1;` ... There is no way to get the variables in an enumerateable manner. – JulianW May 17 '21 at 11:47
  • 3
    tradition approach was: `params_Solver_Last // Do not move, Keep at the end` and a `for` loop. – Jeffrey May 17 '21 at 11:48
  • And you cannot neither retrieve names. No reflection yet in C++. – Jarod42 May 17 '21 at 11:48
  • [Does this help?](https://stackoverflow.com/a/31836401/15740324) According to this answer, you would still need to know the first and the last values, under the assumption that there are no gaps in between. – Standard_101 May 17 '21 at 11:49
  • As shown this sounds like an xy-problem. What if the enum values are non-contiguous? Can you provide a more realistic use-case? – G.M. May 17 '21 at 11:50
  • 2
    Does this answer your question? [How can I iterate over an enum?](https://stackoverflow.com/questions/261963/how-can-i-iterate-over-an-enum) – acraig5075 May 17 '21 at 12:16

5 Answers5

5

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
3

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
}
rubytox
  • 87
  • 8
  • Why `std::initializer_list`? Why not just an array? – eerorika May 17 '21 at 12:06
  • Actually I don't see why I went with an `initializer_list`, an array would be just fine – rubytox May 17 '21 at 12:13
  • If I use an `std::array` I still have to specify the number of elements. I can use an `std::vector` but I'm not sure whether it is the right way to do that then – rubytox May 17 '21 at 12:23
  • 1
    You don't need to use std::array, and you definitely shouldn't use std::vector for this. You can use a regular array instead: `constexpr params_Solver members[] {...};`. With the deduction guides of C++17, you can use std::array and don't even need to specify the element type: `constexpr std::array members {...};`. A pre-C++17 std::array solution is also possible with `constexpr auto members = make_array(...);` given a clever implementation of `make_array` which was suggested for standardisation in a TS, but dropped due to being obsoleted by deduction guides. – eerorika May 17 '21 at 12:33
  • Oh right I understand. Thank you very much, I'll update my answer. – rubytox May 17 '21 at 12:43
1

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.

Chris Uzdavinis
  • 6,022
  • 9
  • 16
  • Thanks! I am actually reading the enums from a different file which is not in my control. I basically want to write the values of the enum into a different file. – Morpheus May 17 '21 at 12:08
  • @Morpheus what do you mean? representation of enum is an integral data (usually an int) and serialized as such. What format your file is in? – Swift - Friday Pie May 17 '21 at 13:55
1

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";
    }
}
Eljay
  • 4,648
  • 3
  • 16
  • 27
1

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524