27 ms·
Learning that you can use unions in C for grouping things into namespaces
- sesuximo 5y agoDoesn’t matter for C, but in C++ this could make your contexpr functions UB since you can only use one member of a union in constexpr contexts (the “active” member).
- pjmlp 5y agoIn C++ we have namespaces for 30 years now, no need for such tricks.
- comex 5y agoC++ namespaces are unrelated to this. They don’t accomplish the same thing.
- pjmlp 5y agoThe goal of inline namespaces is exactly to allow for migrating libraries across versions.
- comex 5y agoThat's nice, but the blog post doesn't say anything about migrating libraries across versions? Looking at the comment thread, I see tialaramex's sibling comment suggested the blog post was about migration, but it's not. I suppose migration is another possible use case for the union trick, and for that case C++ inline namespaces can be used as part of an implementation that achieves a broadly similar goal, but in a completely different way. As tialaramex notes, with inline namespaces you still end up with two different types.
- tialaramex 5y agoHmm. How do C++ namespaces help with the structure naming problem in this example? They seem completely orthogonal. C++ namespaces are a way to avoid library A's symbol "cow" clashing with library B's symbol "cow" without everything being named library_a_cow and library_b_cow all over the place which is annoying. I agree C would be nicer with such a namespace feature. However this technique is about what happens when you realise your structure members x and y should be inside a sub-structure position, and you want both: d = calculate_distance(s.x, s.y); // Old code and d = calculate_distance(s.position.x, s.position.y); // New ... to work while you transition to this naming.
- pjmlp 5y agoYou can use inline namespaces for versioning symbols. https://www.foonathan.net/2018/11/inline-namespaces/ https://www.foonathan.net/2018/11/inline-namespaces/
- tialaramex 5y agoFirst of all, C++ 11 may feel like thirty years ago, and certainly some of its proponents look thirty years older than they did at the time, but it was only ten years ago. C++ namespaces date to standardisation work (so after the 1985 C++ but before the 1995 standard C++) but they don't get this job done. Inline namespaces are a newer feature. Secondly this technique does something different. The C hack doesn't touch the old code. But this "inline namespace" trick means old code has to explicitly opt into this backward compatibility fix or else it might blow up. Lastly, I didn't try this, but presumably you did. Are the two separately namespaces classes the "same thing" as far as type checking is concerned? A vital feature of this union trick is that it's just one structure, it type checks as the same structure because it is the same structure. At a glance, I think the C++ solution results in two types with similar names, so that would fail type checking.
- pjmlp 5y agoAh, another of those threads, ok lets set the years straight. Yes, inline namespaces were only introduced in C++11, about 10 years ago, now lets dive into article. "Learning that you can use unions in C for grouping things into namespaces" Grouping into namespaces, so when did C++ get said feature? ANSI/ISO C++89 released to the world in September 1998, which makes around 23 years, or 24 years if we consider the release of C++ compilers already supporting it the year before, like Borland C++. This C hack definitly does touch old code, as it requires the code to be written to take advantage of the technique and is also touched again, when changes to the structs are required. And naturally recompilation. With inline namespaces, assumign recompilation you can naturally also change which set of identifiers and type aliases are visibile by default.
- ferdek 5y agoIn other words: please always be wary of differences in C and C++, for instance type punning [0]. [0] https://stackoverflow.com/a/25672839 https://stackoverflow.com/a/25672839
- pjmlp 5y agoTriggering UB is a compiler error in constexpr code. https://shafik.github.io/c++/undefined%20behavior/2019/05/11/explporing_undefined_behavior_using_constexpr.html https://shafik.github.io/c++/undefined%20behavior/2019/05/11...
- sesuximo 5y agoTrue, you’ll hopefully get a compiler error.
- midjji 5y agoConstexpr unions is the sane/safe way to use them. Its great, because accessing a member which isnt the last one written, constexpr will explicitly prevent it compile time. Whereas all other examples here are explicitly undefined behaviour!
- rightbyte 5y agoI don't regard this as a "perverse" hack. If I ever do embedded memory mapped stuff in C11 this is way too tempting.
- midjji 5y agoYou are practically guaranteed to invoke undefined behaviour if you do. Just use a map on a std::array of e.g. std::byte
- dexterhaslem 5y agothey said C11 tho
- flohofwoe 5y agoI'm using anonymous nested structs extensively for grouping related items, but I consider the extra field name a feature, not something that should be hidden: https://github.com/floooh/sokol-samples/blob/bfb30ea00b5948f2029c7dd228cf30de08b8036c/sapp/shdfeatures-sapp.c#L136-L165 https://github.com/floooh/sokol-samples/blob/bfb30ea00b5948f... (also note the 'inplace initialization' which follows the state struct definition using C99's designated initialization)
- remram 5y agoThe first example seems wrong, instead of `struct sub { ... };` what is meant is `struct { ... } sub;`
- siebenmann 5y agoYou're right; thanks for noticing and I've updated the first example. My C is a bit rusty these days and I didn't check it with a compiler the way I should have. (I'm the author of the linked-to article.)
- adamnemecek 5y agoDon't actually do this.
- sp332 5y agoThe Linux kernel is using this for bounds checking. https://news.ycombinator.com/item?id=28015263 https://news.ycombinator.com/item?id=28015263
- ufo 5y agoLike the parent poster, when I read the article I assumed that there was no conceivable reason to ever use this feature in a real C program. Let me just say that I'm pleasantly surprised to be proven wrong!
- 10000truths 5y agoAnonymous nested structs are also quite useful for creating struct fields with explicit offsets: #include <stdio.h> #include <stdint.h> #define YDUMMY(suffix, size) char dummy##suffix[size] #define XDUMMY(suffix, size) YDUMMY(suffix, size) #define PAD(size) XDUMMY(__COUNTER__, size) struct ExplicitLayoutStruct { union { struct __attribute__((packed)) { PAD(3); uint32_t foo; }; struct __attribute__((packed)) { PAD(5); uint16_t bar; }; struct __attribute__((packed)) { PAD(13); uint64_t baz; }; }; }; int main(void) { // offset foo = 3 // offset bar = 5 // offset baz = 13 printf("offset foo = %d\n", offsetof(struct ExplicitLayoutStruct, foo)); printf("offset bar = %d\n", offsetof(struct ExplicitLayoutStruct, bar)); printf("offset baz = %d\n", offsetof(struct ExplicitLayoutStruct, baz)); return 0; }
- gumby 5y agoOne 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.
- cjaybo 5y ago> 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 https://godbolt.org/z/7MbcqhnoK
- dataflow 5y agoTry struct S { struct { int x; }; }; under -pedantic and you'll get warning: ISO C++ prohibits anonymous structs [-Wpedantic]
- Subsentient 5y agoBleurgh. I have a deep soft spot for C, and I'm known to get twisted pleasure from using obscure language features in new ways to annoy people, but this is a level of abuse that even I can't get behind. If you need namespacing, use C++. As much as I love C, it's terrible for large projects.
- PostThisTooFast 5y agoI will point out, however, that you can abuse "struct" in a similar way to simulate namespaces in Swift.
- vbezhenar 5y agoLinux kernel is large project and clearly C is sufficient for it, given the fact that migrating to C++ would probably be very easy (not using all C++ features, but just selected ones), yet it did not happen. I think that C++ is better than C, but C is not that bad, even for large projects.
- adtac 5y agothe kernel has to live with the choices it made in the 90s, you don't
- midjji 5y agoYeah they should have upgraded to some restricted subset of C++ or new restrictive language ages ago. I mostly buy the arguments against having exceptions, perhaps even against polymorphism in general, but the argument against destructors, or atomics... hell no.
- humanrebar 5y ago> ...against polymorphism... C has polymorphism. Inheritance-based virtual dispatch is just one kind of polymorphism. It's common to wire up polymorphism in C with bespoke data structures using tagged unions it function pointers. Changing an implementation at link time is even a form of polymorphism.
- kevin_thibedeau 5y agoThe result is uglier and less maintainable than a pair of macros. Or just stop trying to hide syntax. This is ultimately on the same level as typedefing pointers.
- bruce343434 5y agoImo this is not “perverse”. In my vector library I alias a vec3 as float x,y,z and float[3] using this technique.
- midjji 5y agoThis is also known as the most common invocation of undefined behaviour in game programming. If you do this, write to y, then read from [1]. You are invoking undefined behaviour, and compilers doing different things here between windows, linux mac, and different compiler versions is a common cause of "why isnt my game working right on XXX, it works fine on YYY questions.
- saagarjha 5y agoI don’t see the undefined behavior here?
- shultays 5y agoOp probably means cpp, where it is indeed undefined behavior. not sure about c. I doubt that if this would cause a "my game does not work on XXX" though. Is there really a compiler out there that will handle such abuse differently?
- bruce343434 5y agoI don't mean cpp
- midjji 5y agoyes its undefined behaviour in both C and C++. Yes, a number of compilers treat this differently, its also poorly supported on custom hardware using standard compilers like gcc. So compiling for some mobile device with slightly custom ... good luck.
- bruce343434 5y ago
- midjji 5y agoThis is probably a terrible idea, remember that if you have written one member of a union, all other members remain public, yet accessing any of them in any way is undefined behaviour. This is made way worse by most compilers mostly choosing to let you do what you think it will. They just dont guarantee they always will or in all cases.
- drfuchs 5y agoI believe you are mistaken. The C11 standard, section 6.5.2.3 "Structure and union members" pgf 6, says "One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members." And that seems to be what's being used here.
- midjji 5y agoNo: from https://en.cppreference.com/w/cpp/language/union https://en.cppreference.com/w/cpp/language/union. The union is only as big as necessary to hold its largest data member. The other data members are allocated in the same bytes as part of that largest member. The details of that allocation are implementation-defined but all non-static data members will have the same address (since C++14). It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union. What 6.5.2.3 simplifies is the use of unions of the type: struct A{int type; DataA a;} struct B{int type; DataB b;} union U{A a;B b}; U u; switch(u.type)... Its not what is beeing used here. std::variant is designed to deprecate all legitimate uses of union
- throwaway17_17 5y agoYour response to GP is based on the C++ reference and his explicitly is based on the C standard. Your assertion that ‘ [t]he details of that allocation are implementation-defined but all non-static data members will have the same address (since C++14)’ seems to directly conflict with the C11 standard. Also, your closing comment about std::variant is clearly only applicable to C++. I am just curious why you are using C++ when the article and GP are specifically addressing C?
- PaulHoule 5y agoThe C programming language, brought to you by Cthulhu. You don't need eval(), you've got strcpy()!