Hacker News new | ask | show | jobs
by gumby 1779 days ago
One of the very few things from C that I miss in C++ is anonymous structs and enums. I really don’t understand why they are not allowed.

That is, C style enums don’t have to have a name but “type safe” (enum class) ones do. One classic use is to name an otherwise boolean option in a function signature; there’s typically no need to otherwise name it.

C++ incompatibly requires a name for all struct and class declarations, again a waste when you will only have a single object of a given type.

4 comments

> I really don’t understand why they are not allowed.

I don't, either. Such were in D from 2000 or so.

I also don't understand why `class` in C++ sits in the tag name space. I wrote Bjarne in the 1980s asking him to remove it from the tag name space, as the tag name space is an abomination. He replied that there was too much water under that bridge.

D doesn't have the tag name space, and in 20 years not a single person has asked for it.

This did cause some trouble for me with ImportC to support things like:

    struct S { ... };
    int S;
but I found a way. Although such code is an abomination. I've only seen it in the wild in system .h files.
The only explanation I saw was that C++ standards guys were horrified by the idea of unpredictable side effects as a result of initialization of a struct.

I think C++ though is adding them.

What I'd like in c is designated function parameters.

  // these the same
  bar(.a = 10, .b = 12);
  bar(.b = 12, .a = 10);
I suspect that to many C++ programmers, most initializations of structs have unpredictable side effects because of how complex they are ;)
You can somewhat fake it by replacing your functions parameters list with a single struct parameter.

    struct bar_arguments {
       int a, b;
    };
    int bar(struct bar_arguments args) { return 2*args.a + args.b;}

    #define bar(...) bar((struct bar_arguments) {__VA_ARGS__})

    // usage (will print 32 three times)
    printf("%d\n", bar(10, 12));
    printf("%d\n", bar(.a = 10, .b = 12));
    printf("%d\n", bar(.b = 12, .a = 10));

The main drawback is that all parameters are now optional: it will not complain if you forget to assign all parameters, it will silently set them to 0 :-/

    printf("%d\n", bar(10));
    printf("%d\n", bar(.a = 10));
    printf("%d\n", bar(.b = 12));
will print 20, 20 and 12.

You can change those "default values", but then calling the function with regular positional parameters is impaired :-/

> The only explanation I saw was that C++ standards guys were horrified by the idea of unpredictable side effects as a result of initialization of a struct.

I don't understand. How would struct or class initialization be any different from simply doing, say, `for (auto& a : { x, y, z }) frob (a);` which is perfectly legal?

I didn't mention. I think the thought was with designated initializers the order of initialization is what? The order of the elements of the struct? Or the order where it's initialized. In C probably matters little as side effects are usually blatant. C++ I think cryptic side effects are common.
> C++ incompatibly requires a name for all struct and class declarations

You're right about "enum class", but anonymous classes and structs are perfectly valid in C++:

https://godbolt.org/z/7MbcqhnoK

Try

  struct S { struct { int x; }; };
under -pedantic and you'll get

  warning: ISO C++ prohibits anonymous structs [-Wpedantic]
Pedantic is for the older C++ standard, its not pedantic for the latter e.g c++11, I think this changed.
No, pedantic is for disabling compiler extensions. You still need to explicitly specify a standard.
gcc pedantic ignores the language flag, and clang and intel state they mirror gcc. So pedantic would be not C++11 even if you added that.
Well that blows my mind, I never realized pedantic ignores the language setting. Is this the only case where it does that?
... ? That's definitely not true, both anonymous structs and enums work fine in c++.

https://wandbox.org/permlink/ICaQJXCaVOt9mXdP

No, they are forbidden by the standard (take a look at cppreference). Some compilers implement the C behavior as an extension, so tell your compiler to follow the standard strictly.

I don’t use extensions, even convenient ones, as I have to be able to run my code on a variety of compilers. If you don’t have to do that, some extensions (like this one) are really handy.

From the standard, the enum-name is marked as optional in the enum grammar: in [dcl.enum](https://eel.is/c++draft/dcl.enum#11) ; it is also referenced e.g. in [dcl.dcl]:

> An unnamed enumeration that does not have a typedef name for linkage purposes ([dcl.typedef]) and that has a first enumerator is denoted, for linkage purposes ([basic.link]), by its underlying type and its first enumerator; such an enumeration is said to have an enumerator as a name for linkage purposes.

And for classes/structs, [class.pre](https://eel.is/c++draft/class.pre#def:class,unnamed) has explicit wording:

> A class-specifier whose class-head omits the class-head-name defines an unnamed class.

So both are entirely fine (and likewise, unions are too).

Note that my links are for the current draft, but I just checked and this was already the case as far back as C++11. So I wonder where this persistent myth seems to come from.

This is awesome. I referenced cppreference, but that is not authoritative. Unfortunately, in the final draft, [class.pre] grammar makes the name mandatory even though the language you quote remains in the first textual paragraph following the grammar specification!

The part of enums you quoted was C-compatible enums; anonymous scoped enums are explicitly forbidden: "The optional enum-head-name shall not be omitted in the declaration of a scoped enumeration" (dcl.enum 2).

Sigh. I will send in a clarification at least on the class/struct/union side. Ideally the grammar would be fixed rather than that paragraph.

The draft I looked at is https://timsong-cpp.github.io/cppwp/n4868/ (2020-10-18, shortly after the standard was approved).

> Unfortunately, in the final draft, [class.pre] grammar makes the name mandatory even though the language you quote remains in the first textual paragraph following the grammar specification!

That's pretty strange, considering e.g. this paper for quite some time ago: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p022... which is written as if anonymous structs always were a thing.

I wonder if there isn't a deep confusion somewhere where "anonymous" and "unnamed" mean different things to different persons.

Use a enum in a namespace, or anonymous namespace
This is an example of the desired use case:

    static obj& some_call (obj& o, enum struct { abandon, save } disposition) { ... };
This is a common case (and should be more common) to avoid using an obscure boolean flag, which can lead to bugs. It shouldn't need a name.

An anonymous namespace just means the name itself won't leak out; under C++ rules I need the name even to specify the enum tag, which is absurd.

Each instance of "enum struct { abandon, save }" would denote a different type, yes? How would you write a compatible definition to go with your prototype?
I don’t care that they are different types; if anything that would be a feature.

The point is to prevent the “mysterious bool arguments” class of error.

The question is if ADL could infer the scope of the enum, as template instant is toon can now infer the right thing and don’t always need the <T> notation.

I agree that you would want them to be different types; otherwise you would just use an unscoped enum. But that implies that if you write…

  /* example.h */
  void f(enum struct { x, y } arg);

  /* example.c */
  void f(enum struct { x, y } arg) {
    /* do something with arg */
  }
…then you've just created a function with two different overloadings based on distinct anonymous types which just happen to be spelled the same way. Without a type name I don't see any way you could define a function whose prototype would be compatible with the forward declaration. You also have conflicting definitions of "x" and "y" with the same names and scope but different types. Perhaps with GNU extensions you could use typeof(x) for the argument and avoid the conflict, but that isn't standard C++.