3

C++11 furnishes an improved enum with enum struct. But this still suffers from what is surely - until you get inured to it - the most surprising pitfall of the inferior old enum: that the value of a variable of type enum struct E need not be any of the enumerated ones but may be any value in the range of E's underlying integral type:

enum struct X { one = 1};
X x = X(); // Fine! The value of x is int(0)
x = X(2); // Cool! The value of x is int(2)

Everywhere you use an object of enum or enum struct type E, you have to catch the case in which it "isn't one of the Es".

How might one define a generic type that would be serviceable in lieu of enum struct (not necessarily a drop-in substitute) with the property that an object of an instantiating class cannot assume values other than the "enumerated" ones?

I mean cannot in a sense that is satisfied if the object will throw rather than assume any undistinguished value (i.e. it catches the case it which it would become "not one of the Es").

And I say "enumerated" in scare-quotes because it seems unavoidable (without resort to macros) that these values would be "enumerated" by a sequence of integral type template parameters and could not be accessed so conveniently as X::one.

If that is unavoidable it is fine, as long as the "enumerated" values become constants statically retrievable by enumeration index from the type. (It would then be simple for client code to associate meangingful symbols with either the indices or with the indexed values, and to encapsulate such a convenient mapping - e.g. in a struct-nested anonymous enum!)

Is there already a well-regarded solution to this question that I don't know about?

Continued by commmentator request (Ali)

Could you post some pseudo-code? It should show how you would like to use it.

Here are some indications of the desired usage (I think):

/* 1 */
/*  These 2 structs could just be tag-types X{}, Y{}
    serving to distinguish value-safe-enums that
    would otherwise be the same. But you could
    also get more mileage out them, as shown...
*/
struct turn;
struct hand;

using vs_turn = val_safe_enum<turn,int,1,2>;
using vs_hand = val_safe_enum<hand,int,1,2>;

struct turn
{
    // Want an anonymous scoped enum; only rvalue 
    enum {
        right = vs_turn::which<0>(), // = 1
        left = vs_turn::which<1>() // = 2
    };
};

struct hand
{
    enum {
        right = vs_hand::which<0>(), //= 1
        left = vs_hand::which<1>() // = 2
    };
};
/* End 1 */

/* 2 */
char const * foo(vs_turn const & t) {
    // Only 2 possibilities!
    return int(t) == turn::right ? "right turn" : "left turn";
}
char const * foo(vs_hand const & h) {
    return int(h) == hand::right ? "right hand" : "left hand";
} 
/* End 2 */  


vs_turn t1(0); // 3.a OK
vs_turn t2(turn::right); // 3b. OK
vs_hand h1(hand::left); // 3c. OK
vs_hand h2(1); // 3d. OK

t1 == vs_turn(turn::right); // 4. OK

t1 < t2; // 5. OK

t1 = t2; // 6. OK

int(t1) == turn::right; // 7. OK. Explicit conversion to underlying type.

/* 8 */ 
switch(int(t1)) {
case turn::right:
    /* Something */ break;
case turn::left:
    /* Something */;
    // No default necessary! 
}
/* End 8 */

vs_turn t3(3); // 9. Throw! Value out of range
vs_turn t4; // 10. Error. No default construction.

t1 == turn::right; // 11a. Error. No Conversion
t1 <= h1; // 11b. Error. No conversion.
t1 = turn::right; // 11c. Error. No conversion
t1 = h1; // 11d. Error. No conversion.
foo(turn::right); // 11e. Error. No conversion
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182

7 Answers7

1
struct C
{
    enum E { a, b, c };

    C(E e) : e(e) { if (e > c) throw logic_error; }

private:
    E e;
};

Update:

template<typename T, T... E> struct Check;

template<typename T> struct Check<T> { void check(T) { throw logic_error; } }

template<typename T, T e0, T... E> struct Check<T, e0, E...>
{
    void check(T e)
    {
        if (e != e0)
            Check<T, E>::check(e);
    }
}

template<typename T, T... E>
struct C
{
    C(T e) : e(e) { Check<T, E...>::check(e); }

private:
    T e;
}
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • I am asking for a generic solution – Mike Kinghan Jun 16 '13 at 10:30
  • @MikeKinghan: It is not clear what you mean by "generic solution". Why is my solution not generic? – Andrew Tomazos Jun 16 '13 at 10:47
  • "Generic" as in "generic datatype", "generic algorithm" (templates). A solution that can be instantiated for an arbitrary sequence of constants `t0,..,tn` of arbitrary integral type `T` such that an instantiating class `C` will conveniently deliver the *i*th of those constants at compiletime and such that a `C` can only assume the values `t0,..,tn` – Mike Kinghan Jun 16 '13 at 11:10
  • @MikeKinghan: Ok, see my update. Is that a generic solution now? – Andrew Tomazos Jun 16 '13 at 22:12
  • I put this in a toy program with `include ` and `using namespace std`. `int main() { C c(1); (void)c; return 0;}`. I corrected some obvious typos but it needs more than that to compile. gcc 4.8.1, `-g;-O0;-Wall; -std=c++11` See the update to my post in response to Ali. – Mike Kinghan Jun 17 '13 at 09:56
  • How would you use this? `C e(C::a);`? – SirGuy Jun 17 '13 at 14:03
0

What about this?

struct MyColor {
public:
    static MyColor Red; // defined as MyColor(red)
    static MyColor Blue; // defined as MyColor(blue)

    // Copy constructors, assignment etc. goes here
private:
    MyColor(Value value);
    enum Value { red, blue, green };
    Value value;
};
Kit Fisto
  • 4,385
  • 5
  • 26
  • 43
0

It's ugly but it fails at compile-time:

enum E { a, b, c };

constexpr E range_check(E e) {
   return e <= c ? e : (throw "enum value out of range"); // fails at compile time, see below
}

class wrapper {
  public:
    constexpr wrapper(E e) : e(range_check(e)) { }
  private:
    enum E e;
};

int main() {
  constexpr wrapper w((E)42);
}

I don't like this approach mostly because the error message is not helpful. Nevertheless, it is guaranteed to fail at compile-time.

Is this what you are looking for?

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
  • Thank you but no, I am looking for a *generic* datatype (template) that can be instantiated, like the `enum struct` meta-type, for a specified integral type `T` and an arbitrary sequence of `T`s (the enumeration), to give a class `C` in which the enumerated values are indexable compiletime constants and such that a `C` can assume only the enumerated values. – Mike Kinghan Jun 16 '13 at 16:07
  • @Mike Could you post some pseudo-code? It should show how you would like to use it. – Ali Jun 16 '13 at 17:03
0

For the past week I've been looking for a way to do what you're asking for, and more. Here is what I came up with:

#define STD_ENUM_ENTRY_WITHOUT_VALUE__(ENUM, NAME)      NAME,
#define STD_ENUM_ENTRY_WITHOUT_VALUE_(ENUM, NAME)       STD_ENUM_ENTRY_WITHOUT_VALUE__(ENUM, NAME)
#define STD_ENUM_ENTRY_WITHOUT_VALUE(ENUM, SPLIT...)    STD_ENUM_ENTRY_WITHOUT_VALUE_(ENUM, SPLIT)

#define STD_ENUM_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)  NAME = VALUE,
#define STD_ENUM_ENTRY_WITH_VALUE_(ENUM, NAME, VALUE)   STD_ENUM_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)
#define STD_ENUM_ENTRY_WITH_VALUE(ENUM, SPLIT...)       STD_ENUM_ENTRY_WITH_VALUE_(ENUM, SPLIT)

#define FP_PP_ENUM_STD_ENTRY_1(ENUM, SPLIT...)          STD_ENUM_ENTRY_WITHOUT_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_STD_ENTRY_2(ENUM, SPLIT...)          STD_ENUM_ENTRY_WITH_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_STD_ENTRY__(N, ENUM, SPLIT...)       FP_PP_ENUM_STD_ENTRY_##N(ENUM, SPLIT)
#define FP_PP_ENUM_STD_ENTRY_(N, ENUM, VALUE)           FP_PP_ENUM_STD_ENTRY__(N, ENUM, FP_PP_EXPAND VALUE)
#define FP_PP_ENUM_STD_ENTRY(ENUM, VALUE)               FP_PP_ENUM_STD_ENTRY_(FP_PP_NUM_ARGS VALUE, ENUM, VALUE)

#define FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE__(ENUM, NAME)      ::fp::enum_entry<ENUM>(ENUM::NAME, #NAME),
#define FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE_(ENUM, NAME)       FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE__(ENUM, NAME)
#define FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE(ENUM, SPLIT...)    FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE_(ENUM, SPLIT)

#define FP_PP_ENUM_EXT_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)  ::fp::enum_entry<ENUM>(ENUM::NAME, #NAME),
#define FP_PP_ENUM_EXT_ENTRY_WITH_VALUE_(ENUM, NAME, VALUE)   FP_PP_ENUM_EXT_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)
#define FP_PP_ENUM_EXT_ENTRY_WITH_VALUE(ENUM, SPLIT...)       FP_PP_ENUM_EXT_ENTRY_WITH_VALUE_(ENUM, SPLIT)

#define FP_PP_ENUM_EXT_ENTRY_1(ENUM, SPLIT...)          FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_EXT_ENTRY_2(ENUM, SPLIT...)          FP_PP_ENUM_EXT_ENTRY_WITH_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_EXT_ENTRY__(N, ENUM, SPLIT...)       FP_PP_ENUM_EXT_ENTRY_##N(ENUM, SPLIT)
#define FP_PP_ENUM_EXT_ENTRY_(N, ENUM, VALUE)           FP_PP_ENUM_EXT_ENTRY__(N, ENUM, FP_PP_EXPAND VALUE)
#define FP_PP_ENUM_EXT_ENTRY(ENUM, VALUE)               FP_PP_ENUM_EXT_ENTRY_(FP_PP_NUM_ARGS VALUE, ENUM, VALUE)

#define DEFINE_EXT_ENUM(ENUM, ...)                                                          \
    enum class ENUM {                                                                       \
        FP_PP_SEQ_FOR_EACH(FP_PP_ENUM_STD_ENTRY, ENUM, __VA_ARGS__)                         \
    };                                                                                      \
    template<typename>                                                                      \
    struct enum_descriptor;                                                                 \
                                                                                            \
    template<>                                                                              \
    struct enum_descriptor<ENUM> {                                                          \
    public:                                                                                 \
        using enum_type = ENUM;                                                             \
        using entry_type = ::fp::enum_entry<enum_type>;                                     \
        using this_type = enum_descriptor<enum_type>;                                       \
        using const_iterator = entry_type const *;                                          \
        using const_reverse_iterator = std::reverse_iterator<const_iterator>;               \
        using size_type = std::size_t;                                                      \
    private:                                                                                \
        constexpr static std::size_t Size = FP_PP_NUM_ARGS(__VA_ARGS__);                    \
        using container_type = std::array<entry_type, Size>;                                \
                                                                                            \
        constexpr static container_type const _entries                                      \
        {                                                                                   \
            {                                                                               \
                FP_PP_SEQ_FOR_EACH(FP_PP_ENUM_EXT_ENTRY, ENUM, __VA_ARGS__)                 \
            }                                                                               \
        };                                                                                  \
                                                                                            \
        template<std::size_t... Is >                                                        \
        constexpr static char const * name_of_impl(enum_type v, ::fp::indices<Is...>) {     \
            using std::get;                                                                 \
            return ::fp::enum_helper<enum_type>::get_name(v, get<Is>(_entries)...);         \
        }                                                                                   \
                                                                                            \
        template<std::size_t... Is >                                                        \
        constexpr static enum_type value_of_impl(char const * n, ::fp::indices<Is...>) {    \
            using std::get;                                                                 \
            return ::fp::enum_helper<enum_type>::get_value(n, get<Is>(_entries)...);        \
        }                                                                                   \
                                                                                            \
        template<typename V, std::size_t... Is >                                            \
        static bool try_parse_impl(V val, enum_type & res, ::fp::indices<Is...>) {          \
            using std::get;                                                                 \
            return (::fp::enum_helper<enum_type>::is_valid_entry(val, get<Is>(_entries)...)) ? \
                ((res = static_cast<enum_type> (val)), true)                                \
                : false;                                                                    \
        }                                                                                   \
                                                                                            \
        template<typename V, std::size_t... Is >                                            \
        constexpr static enum_type parse_impl(V val, ::fp::indices<Is...>) {              \
            using std::get;                                                                 \
            return (::fp::enum_helper<enum_type>::parse(val, get<Is>(_entries)...));        \
        }                                                                                   \
    public:                                                                                 \
        constexpr enum_descriptor() = default;                                              \
        enum_descriptor(enum_descriptor const &) = delete;                                  \
        enum_descriptor(enum_descriptor &&) = delete;                                       \
                                                                                            \
        constexpr static char const * name() noexcept {                                     \
            return #ENUM;                                                                   \
        }                                                                                   \
                                                                                            \
        constexpr static char const * name_of(enum_type value) {                            \
            return name_of_impl(value, ::fp::build_indices<Size>());                        \
        }                                                                                   \
                                                                                            \
        constexpr static enum_type value_of(char const * name) {                            \
            return value_of_impl(name, ::fp::build_indices<Size>());                        \
        }                                                                                   \
                                                                                            \
        constexpr static size_type size() noexcept {                                        \
            return Size;                                                                    \
        }                                                                                   \
                                                                                            \
        constexpr static const_iterator begin() {                                           \
            using std::get;                                                                 \
            return const_iterator(&get<0>(_entries));                                       \
        }                                                                                   \
                                                                                            \
        constexpr static const_iterator end() {                                             \
            using std::get;                                                                 \
            return const_iterator(&get<(Size - 1)>(_entries) + 1);                          \
        }                                                                                   \
                                                                                            \
        template<typename T,                                                                \
                typename = typename std::enable_if<std::is_integral<T>::value>::type>       \
        static bool try_parse(T value, enum_type & res){                                    \
            return try_parse_impl(value, res, ::fp::build_indices<Size>());                 \
        }                                                                                   \
                                                                                            \
        template<typename T,                                                                \
                typename = typename std::enable_if<std::is_integral<T>::value>::type>       \
        constexpr static enum_type parse(T value){                                          \
            return parse_impl(value, ::fp::build_indices<Size>());                          \
        }                                                                                   \
    };                                                                                      \
    template<>                                                                              \
    constexpr std::array<::fp::enum_entry<ENUM>, FP_PP_NUM_ARGS(__VA_ARGS__)> const enum_descriptor<ENUM>::_entries;

You can view the full code -including an example- on my github.

Although this does what I expect it to do, this can not be (directly) used as a drop-in replacement for your existing enums.

To save yourself a lot of coding, the enums you want to support should be defined like so:

DEFINE_EXT_ENUM(my_1st_enum, (fread, 3), (fwrite), (fflush, fread << 2));
DEFINE_EXT_ENUM(my_2nd_enum, (fopen), (fclose, 1));

One last thing: do not try to compile this on GCC with the -Werr flag, because compilation will error out (I understand why, but I do not currently know how to solve it).

EDIT:

Based on your example, this is how you would do it using my DEFINE_EXT_ENUM:

#include "enum_pp_def.hpp"

#include <cassert>
#include <iostream>

DEFINE_EXT_ENUM(turn, (right,1), (left,2));
DEFINE_EXT_ENUM(hand, (right,1), (left,2));

using turn_descr = enum_descriptor<turn>;
using hand_descr = enum_descriptor<hand>;

/* 2 */
constexpr char const * foo(turn t) {
    return (turn_descr::is_valid((int)t)) ? (turn::right == t) ? "right turn" : "left turn" : throw t;
}
constexpr char const * foo(hand h) {
    return (hand_descr::is_valid((int)h)) ? (hand::right == h) ? "right hand" : "left hand" : throw h;
}
/* End 2 */  

int main(int argc, char ** argv) {
    turn t1 = turn_descr::parse(1); // 3.a OK
    turn t2(turn::right); // 3b. OK
    hand h1(hand::left); // 3c. OK
    hand h2 = hand_descr::parse(2); // 3d. OK

    assert(t1 == turn::right); // 4. OK

    /* 8 */ 
    switch(t1) {
    case turn::right:
        std::cout << "right turn" << std::endl;
        break;
    case turn::left:
        std::cout << "left turn" << std::endl;
        break;
    }
    /* End 8 */

    std::cout << foo(hand::left) << std::endl;
    std::cout << foo(turn::right) << std::endl;

    constexpr turn t3 = turn_descr::parse(3)    // throw at compile time
    turn t4 = turn_descr::parse(3);             // throw at runtime
}
Tom Knapen
  • 2,277
  • 16
  • 31
  • gcc 4.8.1, `-g;-O0;-Wall;-std=c++11` compiles your github example with 7 warnings but runs it apparently correctly. clang 3.2 barfs `./enum_descriptor.hpp:24:32: error: virtual function cannot be constexpr`. Anyhow it isn't possible to initalize one of your enum types with any of the "enumerated" values, e.g. `ext::my_1st_enum en1(3);` barfs; so I regret I wouldn't consider these serviceable in lieu of `enum struct`, or even `enum`. See the update to my post in response to Ali. – Mike Kinghan Jun 17 '13 at 10:40
  • @MikeKinghan Have a look at my edit, I've converted your example to work using my DEFINE_EXT_ENUM implementation – Tom Knapen Jun 17 '13 at 10:54
  • The program does not compile for me with gcc 4.7.2/4.8.1, clang 3.2, `-g;-O0;-Wall;-std=c++11` (having commented out the line that is not supposed to compile). gcc 4.8.1: `error: ‘is_valid’ is not a member of ‘turn_descr {aka enum_descriptor}’`; `error: body of constexpr function ‘constexpr const char* foo(turn)’ not a return-statement`, and the same errors for `hand` in place of `turn`. – Mike Kinghan Jun 17 '13 at 11:34
  • @MikeKinghan That's because you didn't download the latest version of the code from my github, I updated some files about 15 minutes ago to include the `is_valid` function – Tom Knapen Jun 17 '13 at 11:36
  • Sorry I'll retry later today. – Mike Kinghan Jun 17 '13 at 11:39
  • OK for gcc 4.8.1. New errors in clang 3.2. In your `/*2*/`, contrary to mine, the `t` passed to `foo` may be one of the enumerated values *or something else*! - so `foo` has to handle the *something else*. In your `/*8*/` the same goes for `t1` - so the un-defaulted `switch` becomes a fall-through bug. Preceding that `switch` with `turn = turn(0)` provokes it. Such invalid initialization is precisely what I am asking for the type of `t1` to prohibit. The initialization fails `// 9`. For my question, I'm afraid I don't see any any advance over `struct enum`, or indeed `enum`. – Mike Kinghan Jun 17 '13 at 15:08
0

I have wrote this ugly macro that generates wrappers of enums (Not supports C++11 enums :( ). As you can see, the ctor checks if the value passed is a value of the enum:

#include <iostream>
using namespace std;

#define GENERATE_ENUM(NAME,VALUES...) class NAME                                         \
                                      {                                                  \
                                      private:                                           \
                                         static const int _ENUM_LOOKUP_TABLE[];          \
                                         static const unsigned int _lenght;              \
                                      public:                                            \
                                         enum { __VA_ARGS__ };                           \
                                                                                         \
                                         NAME(int value)                                 \
                                         {                                               \
                                             bool trying = true;                         \
                                                                                         \
                                             for(int i = 0; i < _lenght ; ++i)           \
                                                trying = _ENUM_LOOKUP_TABLE[i] != value; \
                                                                                         \
                                             if(trying) throw;                           \
                                         }                                               \
                                      };                                                 \
                                                                                         \
                                      const int NAME::_ENUM_LOOKUP_TABLE[] = { __VA_ARGS__ }; \
                                      const unsigned int NAME::_lenght = sizeof(NAME::_ENUM_LOOKUP_TABLE) / sizeof(int);

GENERATE_ENUM(MyEnum,ONE,TWO,THREE,FOUR,FIVE,SIX)                           

int main()
{
    MyEnum e(33); //This throws an exception (Is not a value of the enum)

    cout << MyEnum::THREE << endl; //Scoped enums, like C++11
    cout << MyEnum::ONE   << endl;
    return 0;
}

For this example, the CPP generates the following code:

#include <iostream>
using namespace std;


class MyEnum 
{ 
private: 
    static const int _ENUM_LOOKUP_TABLE[]; 
    static const unsigned int _lenght; 
public: 
    enum { ONE , TWO , THREE , FOUR , FIVE , SIX }; 

    MyEnum (int value) 
    { 
        bool trying = false; 
        for(int i = 0; i < _lenght ; ++i) 
            trying = _ENUM_LOOKUP_TABLE[i] != value; 

        if(trying) throw; 
    } 
}; 
const int MyEnum ::_ENUM_LOOKUP_TABLE[] = { ONE , TWO , THREE , FOUR , FIVE , SIX };
const unsigned int MyEnum ::_lenght = sizeof( MyEnum ::_ENUM_LOOKUP_TABLE) / sizeof(int);  



int main()
{
    MyEnum e(33); //This throws an exception (Is not a value of the enum)

    cout << MyEnum::THREE << endl;
    cout << MyEnum::ONE   << endl;
    return 0;
}

I think is the simplest macro solution, and covers exactly your problem.

Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • I am afraid this code does not compile for me (gcc 4.7.2, gcc 4.8,1, clang 3.2.) `warning: __VA_ARGS__ can only appear in the expansion of a C99 variadic macro`; `error: ‘THREE’ is not a member of ‘MyEnum’`; `error: ‘ONE’ is not a member of ‘MyEnum’`. And see my replies to user1131467 and Ali. I am not looking for a macro. – Mike Kinghan Jun 16 '13 at 17:06
  • The problem is in C/C++ enums are just intervals of integral constants, enums are not "classes" and we cannot do that type of checkings. So generic solution its impossible (Without macros). – Manu343726 Jun 16 '13 at 17:38
  • Ehh, yes, the code not compiles, and I don't understand why. Im compiling it while Im was writting it (with GCC4.7.2) and most times it compiles :( I don't understand why it not compiles, the anonymous enum is public, and is well-expanded by the macro... – Manu343726 Jun 16 '13 at 17:41
  • You could use a compile-time hasmap (Strings as keys and unsigned ints as values). Yes, is not an enum, but it works *as* a scoped enum... – Manu343726 Jun 16 '13 at 17:42
0

You got bit by a C++03 issue in your C++11 idiom.

The "constructor call" notation X(2) with one argument is actually shorthand for the C-style cast (X) 2, which in turn translates to the toxic reinterpret_cast< X >( 2 ). One of the selling points of scoped enumerations is that they don't convert to and from int.

Avoid using the Type( initializer ) syntax except with class types, and you shouldn't get unexpected values. (Hmm, it would be nice if GCC had a warning for this, but I can't find one.)

Substituting "uniform initialization" X{ 2 } in your code causes the compiler to complain with the proper error.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • I didn't actually get bit that way (very recently); was illustrating the vulnerability of `struct enum` to that bite and want something that doesn't have it. One can't make uniform initialization one's rule because `T{arg}` doesn't always have the same meaning as `T(arg)` and one may not know whether `T` is a class type or not. I'm not sure but the current Standard's current restrictions on `reinterpret_cast` may permit a solution to resist it. – Mike Kinghan Jun 17 '13 at 11:18
0

I have already thought about this a lot, and it requires a Core C++ semantic addition. What you are looking for is unavailable in C++ except for two types defined in the standard: bool and nullptr_t.

Indeed, it is explicitly stated in the standard that objects (and yes, I mean objects, that's the name used in the standard) of type bool have a value that is either true or false. (outside the realm of undefined behaviour) Which means that a (e.g. local) variable of type bool cannot assume, in the realm of defined behaviour, any other value than true or false, although it has storage for many more values. (let's say 256 to simplify) This is taken care of at language level by not describing storage requirements for the bool values, but requirements with regards to the conversion to and from int. (false must convert to 0 and true must convert to 1) There is also a requirement that :1 bitfields must properly convert to and from bool.

Indeed, this does not exist yet in C++. Having a way to define enum types that behave the way you wish would actually imply (and permit) many interesting things. enum types are packed, full, crowded to legal limit. Any value can be converted to an enum type provided that it fits into the underlying type, and you can safely convert it back to int. And you have no "free storage", no value that is not taken by the type.

Now, let's assume that this functionality exists, and that it is called an explicit enum. You could specify that bool maps to this:

explicit enum bool {
    false,
    true
};

Since only two values are "taken", and say "I want to define a type that has all values of bool and a third one:

explicit enum tristate {
    using bool;
    undefined
};

You would then strongly type variables of these types. You could implicitly convert from bool to tristate. You could explicitly convert from tristate to bool. The compiler could propagate conditions on a tristate variable, and the compiler would know that if x != undefined && x != false then x == true, the way it already does with bool variables. It could also determine that there is no need for a default case in your switch statement, maliciously failing compilation every time you try to add a new value to your explicit enum.

You would have to forbid arbitrary conversions from int.

This would be a proposal to write and submit to the committee, a discussion to have on the std-proposals group.

Laurent LA RIZZA
  • 2,905
  • 1
  • 23
  • 41