3
\$\begingroup\$

EnumSpace

This file enables the use of a utility I like to call "EnumSpace". This utility is enums and namespaces combined together in order to create an entity that acts like an enum that is able to inherit and extend an already existing one.

It provides three macros for that purpose:

  1. ENUMSPACE_CREATE
  2. ENUMSPACE_EXTEND
  3. ENUMSPACE_END

ENUMSPACE_CREATE should be used to create a new, independent EnumSpace. It receives the name you would like to give your EnumSpace as an argument.

ENUMSPACE_EXTEND should be used to extend an already existing EnumSpace. It receives two arguments:

  1. The name of the EnumSpace you would like to inherit.
  2. The name of the new EnumSpace.

ENUMSPACE_END should be used like a closing bracket to each of the above.

Usage example:

#include ".../enumspace.hpp"

ENUMSPACE_CREATE( First )         ONE                 ENUMSPACE_END
ENUMSPACE_EXTEND( First, Second ) TWO,   THREE, FOUR  ENUMSPACE_END
ENUMSPACE_EXTEND( Second, Third ) FIVE,  SIX,   SEVEN ENUMSPACE_END
ENUMSPACE_EXTEND( Third, Fourth ) EIGHT, NINE,  TEN   ENUMSPACE_END

int main()
{
std::cout
<< Fourth::ONE
<< Fourth::TWO
<< Fourth::THREE
<< Fourth::FOUR
<< Fourth::FIVE
<< Fourth::SIX
<< Fourth::SEVEN
<< Fourth::EIGHT
<< Fourth::NINE
<< Fourth::TEN;
}

Output:

0 2 3 4 6 7 8 10 11 12

You probably noticed the inconsistency. The ability to stitch EnumSpaces comes with a price: it is impossible to retain a straight streak of numbers automatically.

[ONE = 0, _last( = 1)]
[_first = _last = 1, 2, 3, 4, _last( = 5)]
[_first = _last = 5, 6, 7, 8, _last( = 9)]
[_first = _last = 9, 10, 11, 12, _last( = 13)]

You CAN manually declare the first element you add to be "first" (-> the previous inherited namespace`s "last"):

ENUMSPACE_CREATE( First )         ONE                          ENUMSPACE_END
ENUMSPACE_EXTEND( First, Second ) TWO   = _first, THREE, FOUR  ENUMSPACE_END
ENUMSPACE_EXTEND( Second, Third ) FIVE  = _first, SIX,   SEVEN ENUMSPACE_END
ENUMSPACE_EXTEND( Third, Fourth ) EIGHT = _first, NINE,  TEN   ENUMSPACE_END

Which will result in:

[ONE = 0, _last( = 1)] V  V  V
[_first = _last = 1,  |1, 2, 3,| _last( = 4)]
[_first = _last = 4,  |4, 5, 6,| _last( = 7)]
[_first = _last = 7,  |7, 8, 9,| _last( = 10)]
                  

Or in clear output:

0 1 2 3 4 5 6 7 8 9

Please note that, even though the macro seems to work OK, it relies on behavior I'm not quite sure is defined.

The macro adds a _first and _last elements to any EnumSpace created to facilitate the "stitch" between them.

Now, what would happen when you extend the EnumSpace more than once? There would be more than one _last for the new _first to pick from...

The macro seems to work just fine, and my guess is this happens because whenever the macro asks for the previous EnumSpace's _last, it receives the one in the closest scope, which is the latest inherited EnumSpace's. It's rather ambiguous, however, and your compiler might scream at you. Consider yourself warned.

One last note: You'll probably want to prefix your values with something when using this macro so that you can filter the namespaces names that get inherited along out of auto completion.

#define ENUMSPACE_CREATE(EnumSpaceName) \
    namespace EnumSpaceName {\
        enum {

#define ENUMSPACE_EXTEND(EnumSpace, NewEnumSpaceName) \
    namespace NewEnumSpaceName {\
        using namespace EnumSpace;\
        enum { _first = EnumSpace::_last,

#define ENUMSPACE_END \
    , _last }; }

Will this work? I mean, I tested this. As you can tell by the output provided. But, if something is fundamentally wrong with this, please let me know.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Can you give an example of when this is useful? \$\endgroup\$ Oct 6, 2015 at 21:44
  • \$\begingroup\$ @LokiAstari Haha... I actually did this mostly out of spite because there are about 7 questions on SO in which people answered to questions along the lines of "Can I inherit enums?" with either "No, you can't" or some verbose monstrosities. Of course, I found those questions because I was looking for this functionality myself. Trying to extend an enum to provide function status as return type (Success, File not found, etc.). \$\endgroup\$ Oct 6, 2015 at 21:48
  • 1
    \$\begingroup\$ It'll probably help if I say that the extension was necessary for class inheritance where the more basic classes provide common statuses/error types and as things go lower there are some more specific things. The "deeper" enum values don't need to ride upstream towards the base classes - the extension is simply there to help keep the common types while adding more onto them. \$\endgroup\$ Oct 6, 2015 at 21:53

1 Answer 1

3
\$\begingroup\$

You have already identified that it's inconvenient for users to have a gap in their values. That's easily remedied by starting the values immediately after the inherited ones:

 _first = EnumSpace::_last - 1

A more convenient set of macros would use a variadic argument list to splice the values into the enum, no longer dependent on the user remembering ENUMSPACE_END, and always automatically balanced:

#define ENUMSPACE_CREATE(EnumSpaceName, ...)    \
    namespace EnumSpaceName {                   \
        enum {                                  \
            __VA_ARGS__,                        \
            _last                               \
        };                                      \
    }

#define ENUMSPACE_EXTEND(BaseSpace, NewSpace, ...)      \
    namespace NewSpace {                                \
        using namespace BaseSpace;                      \
        enum {                                          \
            _first = BaseSpace::_last - 1,              \
            __VA_ARGS__,                                \
            _last                                       \
        };                                              \
    }

I think this looks neater where it's used (and it makes Emacs indentation happier than the bare unbraced lists):

ENUMSPACE_CREATE(First, ONE)
ENUMSPACE_EXTEND(First, Second, TWO, THREE, FOUR)
ENUMSPACE_EXTEND(Second, Third, FIVE, SIX, SEVEN)
ENUMSPACE_EXTEND(Third, Fourth, EIGHT, NINE, TEN)

#include <algorithm>
#include <array>
#include <iostream>
#include <iterator>

int main()
{
    using namespace Fourth;
    std::ranges::copy(std::array<int, Fourth::_last>{
            ONE, TWO, THREE, FOUR, FIVE,
            SIX, SEVEN, EIGHT, NINE, TEN
        },
        std::ostream_iterator<int>(std::cout, ", "));
}

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 

One defect that this improved version doesn't address is that we can't specify the integer type underlying the enum, nor use enum class, with these declarations.

\$\endgroup\$
2
  • \$\begingroup\$ Gotta admit, did NOT expect an answer 7 years later :D \$\endgroup\$ Dec 13, 2022 at 13:08
  • \$\begingroup\$ The question looked lonely with no answers to keep it company - I thought it might like a Christmas present! \$\endgroup\$ Dec 13, 2022 at 13:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.