Live data from Hacker News

Three new utility functions in C++23

mariusbancila.ro

81–90 of 196 posts

Re: Three new utility functions in C++23

#81
post #45

Earlier quoted context omitted.

1.) Functions are fine for this stuff. The compiler can be trusted for its ability to "inline" the language-level `__builtin_unreachable()` or equivalent at the relevant optimization levels. 2.) static_cast is shorter, if you know that the underlying type is int. But even if you know, you might not want to spell out int, to be more robust to code changes, which might involve a change of the underlying type of the cor…

Re 3), all the instructions can be expressed in high level code, and compilers have been able to convert high level code to the actual machine instructions for a while. The issue is exactly which code pattern is detected by which compiler varies a lot, so to avoid having to rely on that there was a strong push to add all these explicit intrinsics. Also std::bytesewap is more readable than the longer reverse+as_writea…

There is one strong difference between byteswap and other operations within .

byteswap semantically works on the object representation of the integer, while all other operations semantically work on the integer value, expressed in powers of two.

The C++ language has no strong requirements on the object representation of the underlying integer. The C++20 guarantee of two's complement also only just expressed in terms of integer value, not bit representation.

Therefor `byteswap` somewhat sticks out. It's possibly useful for `std::endian::little == std::endian::native` or `std::endian::big == std::endian::native`, and for `std::has_unique_object_representations_v == true` (aka. no padding bits) for the corresponding integral type T. CHAR_BITS also change the semantics.

So, in this sense it is quite low level, and you have to check all of these to make use of it, or you have to implicitly rely on implementation defined values of these for the targets you care about.

I'm not saying that byteswap is not useful though. But it has the wrong interface. It reverses sequence of bytes, it does not work on integers.

edit:

Oh, in addition of having no padding bits, you also want no trap representations. I don't know if you can check for that.

edit2:

Apparently, you don't necessarily need to check for padding bits, byteswap does it for you, and fails to compile if there are padding bits:

https://eel.is/c++draft/bit#byteswap-2

I don't see trap representations being handled at all. This might be a defect.

Re: Three new utility functions in C++23

#82
post #47

My first impression is that all three of these are clutter to an already very cluttered language ... 1.) Coming from embedded programming, I can see the utility of `std::unreachable`. But shouldn't this be a compiler directive? Or a standardized #pragma? Can someone more knowledgeable in C++ say whether using functions as markers is a common mechanism in std:: ? 2.)Maybe the example is bad here, as it doesn't even sa…

1. It is a compiler defined function (`__builtin_unreachable()`), but the issue is that MSVC doesn't have it, so you need a different implementation per compiler [0]. Plus, if a new compiler shows up (besides MSVC/GCC/LLVM), you'd need to investigate what the correct way to express `__builtin_unreachable` is. From a compiler perspective, using a function makes the most sense, since that fits into the existing control…

> You are also missing the context of C++ defining operator overloading, so you can call `std::byteswap(0ull)` and get an `unsigned long long` and you can call `std::byteswap(std::uint16_t{0})` and get a 16 bit unsigned integer.

I can believe this is useful in explicitly-typed form, i.e. using std::byteswap with T specified. But letting T be inferred seems quite dangerous: C++ loves changing integer types around all by itself (via type promotion, for example), and byteswap and byteswap are (on UNIXy systems) simply not the same operation. For that matter, byteswap should really only be used on uintN_t.

byteswap(a+b) is just asking for trouble.

Re: Three new utility functions in C++23

#83
>Byte swapping is important when transferring data between system that use different order for the sequence of bytes stores in memory.

That seems like a glaring footgun to me, to the point where I think I must be missing something.

What I want when dealing with endianess are "from_little_endian/to_little_endian", "from_big_endian/to_big_endian" function pairs that expand to either nop or a byte swap depending on the host architecture.

Exposing the byte swapping directly without this layer on top is asking for trouble because every user will have to make sure that they correctly detect the local endianess before attempting a swap. That's the potentially tricky part, not swapping the bytes.

Re: Three new utility functions in C++23

#84

My first impression is that all three of these are clutter to an already very cluttered language ... 1.) Coming from embedded programming, I can see the utility of `std::unreachable`. But shouldn't this be a compiler directive? Or a standardized #pragma? Can someone more knowledgeable in C++ say whether using functions as markers is a common mechanism in std:: ? 2.)Maybe the example is bad here, as it doesn't even sa…

I definitely agree with 1. I find the inclusion of core language features in std:: to be rather disconcerting. I've always thought of std:: as a set of standard useful library functions, separate from actual language features.

Re: Three new utility functions in C++23

#85

Earlier quoted context omitted.

If you have undefined behavior, your program is already broken. No such thing as "more broken"; there's already no theoretical limit to what might happen if it gets triggered.

Undefined behavior is considered worse than crashing, which is typically the alternative when reaching "unreachable" codepaths. Compare these two blocks similar to the article switch (ch) { case 'a': do_a(); return; case 'd': do_d(); return; // ch is guaranteed to be 'a' or 'd' by previous code. default: assert(0); } switch (ch) { case 'a': do_a(); return; case 'd': do_d(); return; default: std::unreachable(); } If t…

I take your point, but you can get the behavior of your first example, while still marking the default branch with std::unreachable(), by asserting the preconditions before the switch. This seems to me to be a pretty general equivalence.

So what does std::unreachable() do here? In this particular case, and with NDEBUG defined and any level of optimization selected, I suspect that, at a minimum, the switch would be replaced as you have shown in all versions - it would take a more complex example to show how std::unreachable() makes a difference. The point is, now we have a choice - and it is one that is being offered without creating any backwards-compatibility issues.

Furthermore, the original function, without assertions, is not guaranteed to crash, with or without std::unreachable(). You need some explicit checks to get a desirable response in the case where a mistake has been made, and that option is just as available whether or not you use std::unreachable().

Therefore, while I agree you have shown that not all broken variants of a given program are equivalent, this does not show that std::unreachable() is harmful.

Re: Three new utility functions in C++23

#87
post #80
post #7

Earlier quoted context omitted.

That's not a good advice. Only if the sender and receiver are guaranteed to be running on little endian architecture you can make such a claim. A better advice is to always consider the endian-ness when designing protocols and have a strategy to handle it.

That’s just a waste of cycles on encoding and decoding, as most/all senders and receivers are LE nowadays.

ARM big endian, so you're wrong, and it takes literally nanoseconds to byte swap.

Re: Three new utility functions in C++23

#88

My first impression is that all three of these are clutter to an already very cluttered language ... 1.) Coming from embedded programming, I can see the utility of `std::unreachable`. But shouldn't this be a compiler directive? Or a standardized #pragma? Can someone more knowledgeable in C++ say whether using functions as markers is a common mechanism in std:: ? 2.)Maybe the example is bad here, as it doesn't even sa…

I definitely agree with 1. I find the inclusion of core language features in std:: to be rather disconcerting. I've always thought of std:: as a set of standard useful library functions, separate from actual language features.

That's what it is. It's a useful utility function over non standard compiler built-ins.

The alternative would be a new keyword, but then the go-to complaint would just be about c++ keyword bloat and source compat regressions

Re: Three new utility functions in C++23

#89
post #75

Earlier quoted context omitted.

Longtime C, C++, and embedded programmer here. 1. Why "should" it be something different? It is semantically part of code flow; making it a pragma breaks that model. This replaces the nonstandard __builtin_unreachable(). 2. This doesn't exist to save typing. static_cast doesn't make sense to me (that reads like a no-op). static_cast introduces a new reserved word which is a big no-no. 3. This is not the same as htonl…

1. Isn't std inadequate in "embedded" programming?

In principle the C++ standard does define a subset of its standard library which is available in Freestanding environments (ie without an operating system).

In practice what is actually available and whether it works satisfactorily varies considerably more than for the hosted environments. Almost everything is up for grabs and so you probably can't rely on the standard much. Your compiler vendor probably couldn't care less what the standard says anyway.

Re: Three new utility functions in C++23

#90
post #80

Earlier quoted context omitted.

That’s just a waste of cycles on encoding and decoding, as most/all senders and receivers are LE nowadays.

ARM big endian, so you're wrong, and it takes literally nanoseconds to byte swap.

Can you provide any numbers showing that more than 0.1% of networked ARM devices are big-endian? Pretty much all modern consumer facing ARM devices (including anything made by Apple as well as Android phones, as well as Nintendo) are little endian either exclusively or per standard configuration.
Post reply on HN