5 ms·
I've become somewhat convinced that no one has written a correct nontrivial C or C++ program since 1989.* My go-to example in C++ is examining a floating-point
by agalunar 3y ago
I've become somewhat convinced that no one has written a correct nontrivial C or C++ program since 1989.* My go-to example in C++ is examining a floating-point number as an integer:
• Cast a (pointer to a float) to a (pointer to a uint32_t). No; even if you make sure the size of a float on your target platform is 32 bits, this is undefined behavior.
• Make a union of a float and uint32_t. No; this works in C (since C99...?), but in C++ "it is undefined behavior to read from the member of the union that wasn't most recently written." [1]
• Use reinterpret_cast<uint32_t>. No; this violates the type aliasing rules, and the behavior is undefined. [2]
• Use reinterpret_cast<unsigned char> or reinterpret_cast<std::byte> and then reassemble these into a uint32_t. Yes! this works, although slightly laborious.
• Use std::memcpy. Yes! This is perhaps the "right" answer. The compiler should recognize the idiom and elide any actual copy.
I'm not even confident that I have that correct. It's an enormous language to fit into one's head; the language specification runs about 500 pages long and the standard library another 1500. [3]
[1] https://en.cppreference.com/w/cpp/language/union https://en.cppreference.com/w/cpp/language/union
[2] https://en.cppreference.com/w/cpp/language/reinterpret_cast https://en.cppreference.com/w/cpp/language/reinterpret_cast
[3] https://github.com/cplusplus/draft/releases/tag/n4917 https://github.com/cplusplus/draft/releases/tag/n4917
*This is a joke, but only barely so.
- e4m2 3y agoSince C++20 the correct, constexpr-friendly approach to your specific problem is std::bit_cast: https://en.cppreference.com/w/cpp/numeric/bit_cast https://en.cppreference.com/w/cpp/numeric/bit_cast.
- agalunar 3y agoAh, yes, thank you for catching my omission!
- pif 3y ago> My go-to example in C++ is examining a floating-point number as an integer Which is something that hardly makes any sense and most professional will never need in their entire career.
- agalunar 3y agoIt depends on what you do! The reason I use this as my go-to example is that I came across the incorrect use of reinterpret_cast for this exact purpose in a previous job (which was firmware-adjacent). edit: are you also expressing disagreement with the claim that few C++ programs are correct (and the implied claim that this is because many people's first-guess approaches are actually incorrect), or just saying that you think this is a bad example of the language being complex or misleading?
- vbezhenar 3y agoSerialization of floating point number makes some sense. Even if you can't reliably read it on other machine, reading it on the same machine should be possible.
- yvdriess 3y agoYes, being able to fwrite() dump floats to binaries is invaluable when working with large datasets. It's common to see binary 'cache' file being created for ASCII inputs (e.g. CSV) when there is any chance of it being fed repeatedly.
- pif 3y ago> fwrite() dump floats to binaries That requires a cast from float* to void*. Reading the floating point number as an integer is not necessary.
- gpderetta 3y ago>[type punning via enum] No; this works in C (since C99...?), but in C++ "it is undefined behavior to read from the member of the union that wasn't most recently written. it is technically UB, but it works as a documented extension on pretty much every compiler.
- agalunar 3y agoIt looks like it's okay even technically! From a section marked "(since C99)": > If the member used to access the contents of a union is not the same as the member last used to store a value, the object representation of the value that was stored is reinterpreted as an object representation of the new type (this is known as type punning). [...] Before C99 TC3 (DR 283) this behaviour was undefined, but commonly implemented this way. https://en.cppreference.com/w/c/language/union https://en.cppreference.com/w/c/language/union
- engeljohnb 3y agoI'm new to learning C. What exactly is the problem with uint32_t my_int = (uint32_t)my_float?
- yvdriess 3y agoThat is not a problem, it simply converts the float value to uint32. What the post mentions is `uint32_t my_int = (uint32_t)&my_float;`
- gpderetta 3y agoThat's a different problem: that simply convert a pointer to float to an uint32 (probably truncating it). What the post mentions is 'uint32_t my_int = *(uint32_t*)&my_float; (Of course you had it right, and HN ate your formatting, but it is funnier this way)
- anonymoushn 3y agoThat does a different thing. It converts the actual number represented by the float to an integer. We want to get an integer that contains the same bit pattern as the float.
- deleted 3y ago[deleted]
- indigoabstract 3y agoThis bugs me as well, since there still isn't a general enough official solution, even with C++20's bit_cast. Unofficially, the C way of type punning(pointer conversions and unions) still works in probably all C++ compilers, but it's UB, so potentially dangerous. I think most use cases for type punning fall in 2 categories: conversions between types with same size in memory, for writing or reading as a new type(ex. read an f32 as an u32) or accessing only a part of the data, like the first or the last 2 bytes in a u32. Ideally, there would be a way to specify that a union is used for type punning and what conversion types are allowed (using something similar to concepts or constraints). That would probably solve most use cases and also allow the compiler to easily detect it.