Is it possible to extend a value type in C++? Alf Steinbach describes how to extend enum values.
Consider if an enum
like the following,
enum class Suit{ spades, hearts, diamonds, clubs };
could be extended like
enum class Suit_with_joker extends Suit { joker };
where
Suit_with_joker
has all the enumerators ofSuit
plus thejoker
enumerator; and- enumerators introduced in
Suit_with_joker
get integer values following those ofSuit
; and - any
Suit
value is also aSuit_with_joker
value.
This would be an example of what I’ll call a value type extension.
The apparently backwards is-a relationship in the last point, where any value of the original type is-a value of the derived type, is characteristic of value type extensions.
C++20 totally lacks support for value type extensions, of enum
types or other types.
Value type ‘is-a’ versus class inheritance ‘is-a’
Direct use of class inheritance to model an enum
extension would give an is-a relationship the wrong way.
As a concrete example, see Listing 1.
struct Suit { int value; constexpr explicit Suit( const int v ) : value( v ) {} static const Suit spades; static const Suit hearts; }; inline constexpr Suit Suit::spades = Suit( 0 ); inline constexpr Suit Suit::hearts = Suit( 1 ); struct Suit_with_joker: Suit { constexpr explicit Suit_with_joker( const int v ): Suit( v ) {} static const Suit_with_joker joker; }; inline constexpr Suit_with_joker Suit_with_joker::joker = Suit_with_joker( 4 ); auto main() -> int { (void) Suit_with_joker::hearts; // OK, has inherited the "enumerators". Suit_with_joker s1 = Suit::hearts; //! C. error, wrong way is-a relationship. Suit s2 = Suit_with_joker::joker; //! No c. error, but should be error. } |
Listing 1 |
So, class inheritance works for picking up the base type enumerators, but it doesn’t work for expressing the backwards is-a relationship between base value type and extended type.
A type safe model of an enum extension
Instead of providing reference conversion via class inheritance, a model of an enum
extension requires value conversion via constructors and/or type conversion operators.
This is how e.g. unique_ptr
works. A unique_ptr<Derived>&
reference is not a unique_ptr<Base>&
reference – there’s no inheritance relationship! But a unique_ptr<Derived>
value converts to a unique_ptr<Base>
value.
When Suit_with_joker
doesn’t inherit Suit
(since that would be the wrong way) it must inherit in the Suit
enumerators from somewhere else. Which means that the enumerators must be defined in parallel enumerator holder classes. With no support for comparisons, data hiding etc., just implementing type safe conversion, it can go like Listing 2.
struct Suit; struct Suit_names { static const Suit spades; static const Suit hearts; }; struct Suit: Suit_names { int value; constexpr explicit Suit( const int v ) : value( v ) {} }; constexpr Suit Suit_names::spades = Suit( 0 ); constexpr Suit Suit_names::hearts = Suit( 1 ); struct Suit_with_joker; struct Suit_with_joker_names: Suit_names { static const Suit_with_joker joker; }; struct Suit_with_joker: Suit_with_joker_names { int value; constexpr explicit Suit_with_joker( const int v ): value( v ) {} constexpr Suit_with_joker( const Suit v ) : value( v.value ) {} }; constexpr Suit_with_joker Suit_with_joker_names::joker = Suit_with_joker( 4 ); auto main() -> int { (void) Suit_with_joker::hearts; // OK, has inherited the "enumerators". Suit_with_joker s1 = Suit::hearts; // OK, right way is-a relationship. #ifdef FAIL_PLEASE Suit s2 = Suit_with_joker::joker; //! C. error, /as it should be/. :) #endif } |
Listing 2 |
Compared to the hypothetical
enum class Suit{ spades, hearts, diamonds, clubs }; enum class Suit_with_joker extends Suit { joker };
… this is a heck of a lot of code; language support would have been nice.
Note: the above code just exemplifies working C++ that implements a type safe enumeration type extension. It does not provide conversion from enumerator to int
, or more generally to the underlying type. And it does not provide a way to specify the underlying type. As mentioned, it does not provide value comparison.
Suit_names
and Suit_with_joker_names
should be non-instantiable. And Suit
and Suit_with_joker
should ideally inherit in the value
data member from some generic Enumeration
class. And there are even more issues, all omitted for clarity, but all mostly trivial.
About an enum extension syntax
In the example I used the word extends
instead of just a colon :
as with classes, because this isn’t like a class inheritance: the is-a relationship goes the opposite way.
And I imagine that a useful syntax would have to provide for a list of base enum
types, not just one.
Case in point: in my own hobbyist code I’ve only used the above scheme once, mostly as an exploration of the issues, and then for data stream id’s hypothetically defined like
enum class Input_stream_id{ in = 0 }; enum class Output_stream_id{ out = 1, err = 2 }; enum class Stream_id extends Input_stream_id, Output_stream_id {};
This supported type safety for functions taking a stream id, since a stream id argument can be limited to input (Input_stream_id
parameter type) or output (Output_stream_id
parameter type), and alternatively can be allowed to be any stream (Stream_id
parameter type).
Probably the Boost Preprocessing Library (BPL) can be used to generate modeling code for enumeration type extensions. I.e. the hypothetical enum
declarations can be replaced with actual C++ macro invocations. And possibly, as a less maintenance-friendly alternative, AI based code generation such as via ChatGPT can be used on a case by case base.
However, regarding BPL-based macros, in my experience ‘smart’ variadic macros lead to brittle and ungrokable code. If or when one chooses to use type safe enumeration extensions, it is perhaps better to just code it up manually, as I did for the stream id’s. I believe that this is an example of a feature that would be used if it was provided by the core language, where the centralized effort provides correctness guarantees and the effort involved confers some advantage to all millions of C++ users.
is a Norwegian C++ enthusiast, currently co-admin of FB groups ‘C++ Enthusiasts’ and ‘C++ in-practice questions (most anything!)’. He’s worked as a vocational school teacher (no C++), as a college lecturer (teaching also C++, and introducing a Windows programming course), and as an IT consultant (mostly C and C++).