Live data from Hacker News

Enum class improvements for C++17, C++20 and C++23

cppstories.com

71–80 of 121 posts

Re: Enum class improvements for C++17, C++20 and C++23

#71
post #59

Earlier quoted context omitted.

they easily can :) void test(int& y){} int main() { int* x = nullptr; test(*x); }

There is a difference between an API promissing that a value wont be null and a buggy program setting a null where it should not. A reference is only null if someone fucked up. As a programmer you can usually rely on a reference not being null and you couldn't do anything about it if it was anyway within the constraints of the language.

In the same way, an `enum class` variable's value being outside of the set defined in the `enum class` is also a fuckup.

Re: Enum class improvements for C++17, C++20 and C++23

#72

All that crap is the reason I never bother with enums in all these C like languages set_player_color(player, .RED); That should be the way to use them, it's concise and typesafe set_color(Color::RED); Why repeat yourself? It's one of the things I love about Swift and Zig int main() { using enum ComputeStatus; ComputeStatus s = NotEnoughMemory; } now you have polluted the scope.. C++ have lost the plot.. they understo…

It's obvious with Color, but when you deal with more complex stuff, code like

    computation_service.set_status(ComputeStatus::NotEnoughMemory);
    system.set_state(SystemState::Failure);
is a lot clearer than

    computation_service.set_state(.NotEnoughMemory);
    system.set_state(.Failure); 
in my opinion. Both objects/structs/whatevers can have a state type of their own but the shorthand form does not indicate whether or not the two can be used interchangeably. As a workaround, you could rename the setter to set_computation_state and set_system_state but then you're just repeating yourself in a different manner that's not as precise.

Re: Enum class improvements for C++17, C++20 and C++23

#73
post #9

enum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; // OK One of their examples demonstrates the number one issue for me with enums, which was not fixed with `enum class`. Since values outside the range of the type are valid, you are constantly needing to check for invalid values in any function that takes an enum [class]. Ruins any attempt at "parse, don't validate" style in c++ and completely ruins the "…

What the alternative? Let’s say you have a file, you parse a uint32_t, and you want to convert that into the Handle type. If the enum is closed how do you do it? Giant switch? That would break the fundamental principle that C++ abstractions are zero-cost.

We could allow zero cost non-conversion from the primitive type (and invalid values at rest as a consequence) but check and complain about invalid enum values at runtime, "opt-in", when we care: in some kind of novel exhaustive pattern matching statement (not an old school switch statement), which can simply treat invalid values as an erroneous default case, or by invoking a synthesized validation method of the enum class (and if possible with validating variations of conversion from the primitive type, copy constructors and the like).

Most enum use is served adequately by never letting in potentially invalid values from untrusted input; an enum variable that is set to an enum constant will necessarily have a valid value. "Decayed" primitive values, like a bitmask formed by bitwise operations between valid enum values, aren't normally intended to be re-ingested as enum values.

Re: Enum class improvements for C++17, C++20 and C++23

#74
post #9

enum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; // OK One of their examples demonstrates the number one issue for me with enums, which was not fixed with `enum class`. Since values outside the range of the type are valid, you are constantly needing to check for invalid values in any function that takes an enum [class]. Ruins any attempt at "parse, don't validate" style in c++ and completely ruins the "…

That's by design. Consider mapping a file or reinterpret casting a network buffer that contains a structure with such an enum: if has been written by a different version of an application, the possible enum values might be different. That was considered, among other thing, an important use case to support. You can easily build your own safe enum on top of you really want. Edit: someone else pointed out the bitmask us…

You can justify the escape hatches for every feature in C++. The problem is that we eschew sensible defaults to handle the edge cases. Without going into a rust war, I think rust's unsafe is a great way to handle this - for 99% of use cases, you _really_ don't want to put an invalid enum value in there. But, in the number of cases where you do, you should have an escape hatch to do so.

If you could do:

    my_enum_type foo(std::vector& buf, int& offset)
    {
        // bounds check omitted for brevity
        // return my_enum_type{buf{offset++]}; // outside an unsafe-style block would cause a compile error, or a throwing constructor. TBD
        std::unsafe {
            return my_enum_type{buf[offset++]};
        }
    }
you would reduce the possible impact areas to places where you explicitly want to do dangerous stuff.

Instead we end up with a feature that is a "zero cost abstraction" which just pushes the bookkeeping onto the user - every switch statemeent needs to handle the case where someone has passed in 42.

Re: Enum class improvements for C++17, C++20 and C++23

#75
post #56

Earlier quoted context omitted.

It wasn't unfixed in C++17. The problem existed before C++17. This is valid code in C++11: enum class Foo : int { Invalid = 0 }; Foo f = Foo(5); In C++11, this compiles with Foo(5) but not Foo{5}. In C++17, this compiles with both Foo(5) and Foo{5}.

IIRC for a brief period GCC considered values outside of the enumeration as UB and heavily optimized according to this. At some point this interpretetion made it as far as at least a draft standard. Then it got reverted as it went against decades of common usages and instead the opposite was made explicit in the standard. Making it UB for enum classes was considered, but the strongly typeded alias use case was consid…

Unfortunate. Then again, constraint value integer types are something that would be useful in general and perhaps can be provided independently of enum class.

Re: Enum class improvements for C++17, C++20 and C++23

#76

Earlier quoted context omitted.

Probably not too much work to add and then also build a JSONLD @context from all of the ~ message structs. :Thing > https://schema.org/name , :URL , :identifier and subclasses Thing > Intangible > Enumeration: https://schema.org/Enumeration

Adding reflection will be simple and backwards compatible with existing code as it would only come into play when someone hasn't manually mapped a type. This leaves the cases where reflection doesn't work(private member variables) still workable too. Haven't looked at JSONLD much, but it seems like it could be added but would be a library above I think. Extracting the mappings is already doable and is done in the JSO…

"Exporting JSON Schema" https://github.com/beached/daw_json_link/blob/release/docs/c... :

  include 
  std::string daw::json::to_json_schema( "identifier", "title" );
From https://westurner.github.io/hnlog/#comment-38526588 :

> SHACL is used for expressing integrity constraints on complete data, while OWL allows inferring implicit facts from incomplete data; SHACL reasoners perform validation, while OWL reasoners do logical inference.

- "Show HN: Pg_jsonschema – A Postgres extension for JSON validation" https://news.ycombinator.com/item?id=32186878 re: json-ld-schema, which bridges JSONschema and SHACL for JSONLD

Re: Enum class improvements for C++17, C++20 and C++23

#77
post #20

Earlier quoted context omitted.

an event loop often wants an event type enum that has defined values for internal events and then everything out of range means pass onto the users handler. There are other variations where you need to pass an enum value without caring what it mean.

This isn't rocket science once you have sum types. enum Event { System(SystemEvent), User(T), }

Using Rusts' retarded enum keyword for your example doesn't exactly make your point understandable, especially when talking in the context of actual enumerations.

Anyway, the point of what gp describes is to do this in a single integer without overhead so a naive sum type is not the solution.

Re: Enum class improvements for C++17, C++20 and C++23

#78
post #9

enum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; // OK One of their examples demonstrates the number one issue for me with enums, which was not fixed with `enum class`. Since values outside the range of the type are valid, you are constantly needing to check for invalid values in any function that takes an enum [class]. Ruins any attempt at "parse, don't validate" style in c++ and completely ruins the "…

That's by design. Consider mapping a file or reinterpret casting a network buffer that contains a structure with such an enum: if has been written by a different version of an application, the possible enum values might be different. That was considered, among other thing, an important use case to support. You can easily build your own safe enum on top of you really want. Edit: someone else pointed out the bitmask us…

I don't think reinterpret casting enums is something that should be encouraged and uncovering such points where unexpected values can be introduced is exactly the point of having a strict enum. Just because putting random values into the enum isn't itself undefined behavior doesn't mean that your program isn't going to blow up later on.

Bitmask enums are also a hack and the better solution is to have different types for the individual flags (an enum) as well as a type for combinations of flags (custom type built around an integer).

Yes, these are existing uses but when enum class was designed there were no existing usages for that.

Re: Enum class improvements for C++17, C++20 and C++23

#79
post #59

Earlier quoted context omitted.

There is a difference between an API promissing that a value wont be null and a buggy program setting a null where it should not. A reference is only null if someone fucked up. As a programmer you can usually rely on a reference not being null and you couldn't do anything about it if it was anyway within the constraints of the language.

In the same way, an `enum class` variable's value being outside of the set defined in the `enum class` is also a fuckup.

no, deferencing a null ptr is UB. An enum class outside the declared values is perfectly valid.

You could design a language feature where integer to enum is checked, but that's not enum.

Enum classes already add scoping, forbid implicit conversions and allow explicit underlying types. Those are pure extensions. Making undeclared values invalid or UB would be very surprising to people used to normal enums.

Re: Enum class improvements for C++17, C++20 and C++23

#80
post #38
post #9

enum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; // OK One of their examples demonstrates the number one issue for me with enums, which was not fixed with `enum class`. Since values outside the range of the type are valid, you are constantly needing to check for invalid values in any function that takes an enum [class]. Ruins any attempt at "parse, don't validate" style in c++ and completely ruins the "…

The main difference between old ("unscoped") and new ("scoped") enums, besides dropping implicit conversions from/to integral types, is the scope of the named constants. With unscoped enums, the constants are in the surrounding scope, which means that constants with the same name but of different enum types collide with each other. One solution is to wrap them in a dummy struct or class (`struct Foo { enum Bar { baz…

> The reason other values of the underlying integral type beyond the named ones are allowed, is that enums are often used to specify bit values or bit masks that you AND/OR with each other. It is what it is.

Except bitwise operations of orign enum values yields the underlying type and not an enum value. And for enum classes the operators don't exist at all. So you need to write custom operators(or manual casts) for this use case anyway so you might as well go all the way and write a proper typed bitset type instead of abusing enums. Allowing this for old enums makes sense for C compat but that doesn't mean enum class couldn't have been stricter.

Post reply on HN