54

Normally you can define a cast for a class by using the following syntax:

class Test {
public:
  explicit operator bool() { return false; }
};

Is there a way to do this or something similar for an enum class?

OmnipotentEntity
  • 16,531
  • 6
  • 62
  • 96
  • 2
    If you did that, what would be the point of using `enum class` at all? `enum` can be forward declared, given an underlying type, and is scoped with the enumeration name. If you can freely convert them from/to integers... why use an `enum class` at all? Because it's new? – Nicol Bolas Oct 05 '12 at 20:31
  • 9
    Because I still don't want to have the `enum class` convert to an integer, but I would be able to define it to be able to be converted to a `bool`, and have specific values evaluate to true and others to false. – OmnipotentEntity Oct 05 '12 at 20:31
  • 1
    Note that there is no such thing as an "implicit cast". A cast is something you write in your source code. It tells the compiler to do a conversion. So a cast is an explicit conversion. The compiler can also do some conversions without a cast; those are known as "implicit conversions". – Pete Becker Oct 05 '12 at 21:29
  • 1
    @NicolBolas I was googling here because I would have liked implicit conversion to bool, where one enum is SUCCESS and the others are error-codes. Obviously, one can put in a comparison, but with the variable names as they were it wasn't needed for clarity. – Andrew Lazarus Nov 08 '14 at 00:01
  • Hmm... one case would be a set of flags for bitwise operations using a C-style bitfield, @NicolBolas. For a class e.g., `MyCBitfield`, It would allow the programmer to define the conversion as `static_cast(1) << bitflag` while preventing conversion to `int`. (Although, at that point, you're admittedly better off making the type constructible from the enum, or providing an assignment operator.) – Justin Time - Reinstate Monica Aug 17 '19 at 01:06

3 Answers3

29

No, it's not.

Actually, an enum class is no class at all. The class keyword is only used because suddenly changing the unscoped enum to a scoped enum would have mean reworking all enums codes. So the committee decided that to distinguish between new-style and old-style enums, the new ones would be tagged with class, because it's a keyword already so no enum could have been named class in C++. They could have picked another, it would not have made much more sense anyway.

However, despite the class keyword they are still regular enums in that only enumerators (and potentially values assigned to them) are allowed within the brackets.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 7
    `enum class` was first used with nearly the same semantics by C++/CLI. The Standard adopted the existing syntax and standardized it. – Ben Voigt Oct 05 '12 at 22:00
  • 7
    Makes sense. Too bad C++/CLI hadn't used `enum namespace`, but it is what it is. – Eljay Nov 14 '17 at 18:00
19

No, but you can make a normal class type act like an enum class, using constexpr members and constructors. And then you can add all the additional member functions you want.


Proof that it can work even with switch:

#include <iostream>

struct FakeEnum
{
    int x;

    constexpr FakeEnum(int y = 0) : x(y) {}

    constexpr operator int() const { return x; }

    static const FakeEnum A, B, Z;
};

constexpr const FakeEnum FakeEnum::A{1}, FakeEnum::B{2}, FakeEnum::Z{26};

std::istream& operator>>(std::istream& st, FakeEnum& fe)
{
    int val;
    st >> val;
    fe = FakeEnum{val};
    return st;
}

int main()
{
    std::cout << "Hello, world!\n";
    FakeEnum fe;
    std::cin >> fe;

    switch (fe)
    {
        case FakeEnum::A:
        std::cout << "A\n";
        break;
        case FakeEnum::B:
        std::cout << "B\n";
        break;
        case FakeEnum::Z:
        std::cout << "Z\n";
        break;
    }
}

Proof that working with switch does not require implicit interconversion with int:

#include <iostream>

/* pseudo-enum compatible with switch and not implicitly convertible to integral type */
struct FakeEnum
{
    enum class Values { A = 1, B = 2, Z = 26 };
    Values x;

    explicit constexpr FakeEnum(int y = 0) : FakeEnum{static_cast<Values>(y)} {}
    constexpr FakeEnum(Values y) : x(y) {}

    constexpr operator Values() const { return x; }
    explicit constexpr operator bool() const { return x == Values::Z; }

    static const FakeEnum A, B, Z;
};

constexpr const FakeEnum FakeEnum::A{Values::A}, FakeEnum::B{Values::B}, FakeEnum::Z{Values::Z};

std::istream& operator>>(std::istream& st, FakeEnum& fe)
{
    int val;
    st >> val;
    fe = FakeEnum(val);
    return st;
}

int main()
{
    std::cout << "Hello, world!\n";
    FakeEnum fe;
    std::cin >> fe;

    switch (fe)
    {
        case FakeEnum::A:
        std::cout << "A\n";
        break;
        case FakeEnum::B:
        std::cout << "B\n";
        break;
        case FakeEnum::Z:
        std::cout << "Z\n";
        break;
    }
    // THIS ERRORS: int z = fe;
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Boost is a good example of this -- look at the part for non-C++11 (`BOOST_NO_SCOPED_ENUMS` is defined) and extend that -- http://www.boost.org/doc/libs/1_50_0/boost/detail/scoped_enum_emulation.hpp – Travis Gockel Oct 05 '12 at 22:15
  • “you can make a normal class type act like an enum class” — Unfortunately you *can’t*, in C++, because C++’ `switch` is stupid (it’s also hard/impossible to make it into a POD while preserving sane invariants such as private constructor). – Konrad Rudolph Feb 13 '18 at 18:22
  • @KonradRudolph You can make your class `switch`-able if you define a single conversion function to integral or enumeration type. What do you mean by "`switch` is stupid"? (if you recall what point you were trying to make there) – villapx Apr 11 '19 at 15:37
  • @villapx It’s stupid because it only works on integral constant values for no good reason other than its history. Modern languages have `switch` statements that are vastly more useful by supporting custom types and powerful pattern matching. By contrast, unless you’re writing (or, more likely auto-generating) a state machine on integer states, the `switch` statement offers virtually no advantage over an `if` statement with multiple `if else` branches. – Konrad Rudolph Apr 11 '19 at 17:08
  • @KonradRudolph: Other languages are stupid because they provide two different features through a single keyword. There is absolutely nothing wrong with providing an efficient jump-table control flow construct, as C and C++ do with `switch`. There is also nothing wrong with providing a pattern-match-based control flow construct. Most modern language do NOT have `switch` statements that act as you describe -- most have "match expressions" or "case statements" or "when" or "select" or.... There is nothing about the word `switch` that implies pattern-matching support. – Ben Voigt Apr 11 '19 at 18:00
  • @BenVoigt That doesn’t change the fact that the `switch` statement in C++ is seriously stunted in its capabilities without valid technical reason. Modern C++ strives to provide *high-level*, versatile constructs that work with custom types. `switch` is the diametric opposite of that. – Konrad Rudolph Apr 11 '19 at 21:38
  • 1
    C++ strives to provide high-level features at the same time as maintaining direct access to the machine ("better assembly language"). The designers have said *many* times, that C++ is multi-paradigm. `switch` is direct access to a jump-table, exactly like it is in C. And a systems-programming language *should* have direct access. I think it would be great if someday C++ does add some pattern matching operators, but it would be a horrible mistake if they used the `switch` keyword for that. – Ben Voigt Apr 11 '19 at 23:44
  • @KonradRudolph: Meanwhile, your comments are wrong starting with your very first assumption. `switch` does work with custom pseudo-enum types just as it does with true `enum` and `enum class` types. – Ben Voigt Apr 11 '19 at 23:52
  • @BenVoigt This design principle stopped being true a long time ago. Even C no longer provides a close-to-the-metal model for modern CPU architectures, *and that’s OK*. Creating a jump table from a list of alternatives is a (trivial) job for the optimising compiler, not the programmer. Your `FakeEnum` example is nice but it breaks several of the conditions I’d have for a strongly typed enum, in particular implicit conversion to `int` (which is required for it to work with `switch`), which means it loses most of the benefits of being its own type. – Konrad Rudolph Apr 12 '19 at 08:28
  • @KonradRudolph: You continue to make comments which are grounded in your lack of imagination, not facts. I chose the combination of "enumerators nested inside the type name" and "implicit conversion to `int`". When you define your own pseudo-enumerators, you can decide both of those independently (unlike `enum` vs `enum class` where the choices are linked). If you want explicit bool but not implicit conversions to/from `int`, [here you are](https://rextester.com/MGJQK39909) – Ben Voigt Apr 12 '19 at 13:25
  • @BenVoigt You misunderstand me: I want *no* implicit conversion; and I want a `StrongEnum` base class that I can inherit from CRTP style to obtain a new, strongly-typed “enum”, without having to write manual code. These are not unreasonable requirements (I’d call them *minimal* acceptable requirements, in fact), but they preclude use inside a `switch`. – Konrad Rudolph Apr 12 '19 at 13:37
  • And, just to clarify: I’d be OK with having an implicit conversion to a non-visible `enum class` as an implementation detail (in your example this would correspond to making `Value` private) because that conversion could never be triggered by accident, the type being unaccessible. However, I see no way of getting this to work with CRTP without having to write this manually for every custom enum type and this is something I really find unacceptable. – Konrad Rudolph Apr 12 '19 at 13:49
  • 1
    @KonradRudolph: Note that the values `A`, `B`, `Z`, don't actually have to exist in the secret enum class type... an enum class object is (unlike a legacy enum) guaranteed to be able to store the same set of values as its underlying type, whether or not they are part of the closure of enumerators under bitwise-OR. – Ben Voigt Apr 12 '19 at 14:00
  • 1
    @BenVoigt I see where you are going with this. Indeed, CRTP can be kind-of made to work with this. – Konrad Rudolph Apr 12 '19 at 15:30
  • Isn't ```constexpr const``` redundant? – LIU Qingyuan Feb 03 '21 at 09:54
5

You cant define non-member cast operators in C++. And you certainly cant define member functions for enums. So I suggest you do free functions to convert your enum to other types, the same way you would implement cast operators.

e.g.

bool TestToBool(enum_e val)
{
    return false;
}

const char *TestToString(enum_e val)
{
    return "false";
}

There is a nice way of associating those enums to bools, you have to split it on two files .h and .cpp. Here it is if it helps:

enum.h

///////////////////////////////
// enum.h
#ifdef CPP_FILE
#define ENUMBOOL_ENTRY(A, B)            { (enum_e) A, (bool) B },
struct EnumBool
{
    enum_e  enumVal;
    bool    boolVal;
};
#else
#define ENUMBOOL_ENTRY(A, B)            A,
#endif


#ifdef CPP_FILE
static EnumBool enumBoolTable[] = {
#else
enum enum_e
{
#endif
ENUMBOOL_ENTRY(ItemA, true),
ENUMBOOL_ENTRY(ItemB, false),
...
};

bool EnumToBool(enum_e val);

enum.cpp

///////////////////////////////
// enum.cpp
#define CPP_FILE
#include "enum.h"

bool EnumToBool(enum_e val)
    //implement

I didnt compile it so take it easy if it has any errors :).

imreal
  • 10,178
  • 2
  • 32
  • 48