7 ms·
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 utilit
by a2800276 4y ago
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 save typing. (18 chars for `std::to_underlying` vs. 16 for `static_cast<int>`. The 'old' variant seems more expressive to boot. If anything, how about `static_cast<auto>`? or <underlying> ...
3.) The usefulness of `std::byteswap` to convert data to network byte order seems trivial vs. the venerable old `htonl` family of functions. `std::byteswap` seems more like intrinsics meant to expose possibly present target machine instructions to the user. Like `std::unreachable` this is probably of most use to embedded / low-level programming. This may be a deficiency in the article...
If it's not obvious, I'm not a big C++ fan and read the article with C-tinted glasses :)
- kazinator 4y ago> But shouldn't this be a compiler directive? Lisp perspective: anything that can be a function almost certainly should be a function (and not a special operator or macro). std::unreachable() does not have any arguments; therefore it doesn't need any special argument evaluation semantics that would require a compiler built-in. The way C and C++ work, compiler directives are keywords and not identifiers. Keywords are not namespaced. Introducing a keyword called "unreachable" is problematic; far more so than a new element in the std namespace. My only problem with std::unreachable is that I would never use it over std::abort. Nobody needs a function whose only job is to invoke undefined behavior (from which it is then assumed that it is not reached). It's a good cold day, so I can almost hear the Rust people laughing in the distance.
- steveklabnik 4y agoTo be clear, Rust has this exact same functionality, with an intrinsic as well as a regular function that wraps said intrinsic. The only difference is that it’s marked unsafe. https://doc.rust-lang.org/std/hint/fn.unreachable_unchecked.html https://doc.rust-lang.org/std/hint/fn.unreachable_unchecked.... There is a safe version that is just a wrapper around panic! with a standardized message.
- flohofwoe 4y ago> ...`std::unreachable`. But shouldn't this be a compiler directive? Probably "committee pragmatism", a stdlib change might be easier to get approved than a language change, and compilers already have builtins for this, they're just not compatible (but the differences can be wrapped in a macro, and since C++ doesn't like to expose macros, it's probably still a macro, but hidden inside a stdlib template). FWIW it looks like C23 will also just get a macro: https://thephd.dev/ever-closer-c23-improvements#unreachable-macro-for-optimization-and-code-improvement https://thephd.dev/ever-closer-c23-improvements#unreachable-...
- gpderetta 4y agoAlso from a standardization point of view there isn't really much of a difference between a builtin and a plain library function. Library functions can be scoped in namespaces and that's a plus.
- addaon 4y agostd::unreachable() feels like it's what std::assert(false) should be, except that std::assert(false) is bizarrely defined (as a no-op) when NDEBUG is defined. This is one of those strange cases where the spec defines behavior that I'd strongly expect to be undefined.
- int_19h 4y agoWhat's bizarre about asserts being disabled if you explicitly ask for it?
- addaon 4y agoI'm not asking for the assert to be disabled. I'm asking for debugging features to be disabled. (NDEBUG == "no debug.") The debugging feature of an assert statement is that it performs work to check if the assertion is true and provides diagnostics if not. Disabling the debugging feature of an assert would, in my mind, make it purely an assertion -- an assumption that the compiler can count on (since this is true by design and demonstrated during debugging). Which is to say, it becomes undefined (contrary to compiler assumptions) if the condition is false. Another way of thinking of this: With NDEBUG undefined, the sequence `assert(condition); if(!condition) action();` is perfectly reasonable and equivalent to the naked assert. But, because assert(false) is defined, defining NDEBUG changes the semantics of the code, with the two examples having identical behavior when NDEBUG is unset, and different behavior when NDEBUG is set. This is contrary to my understanding of the intent of NDEBUG, to suppress debugging information without changing semantics. A third view: With NDEBUG undefined (which is normal for test and development), /I have no way of testing what happens past an assert(false)/. If I then define NDEBUG for a release build, I'm by definition exposing my user to untested code. The compiler might (with a defined assert(false)) define the behavior of this new code, /but I cannot, since I can't test it./ It just makes more sense to acknowledge that anything past an event horizon is undefinable, and call it undefined.
- chrisseaton 4y agoIs there syntax available in C++ to write some kind of instruction to the compiler which is not some kind of call? Even __builtin_trap is a call isn't it? What else could you attach a directive to?
- tlb 4y agoDirectives are attached to functions, like abort() has the [[noreturn]] attribute, so compilers don't have to emit any code that would run after it returns, or save anything on the stack. std::unreachable() goes a step further, and tells the compiler that it doesn't even have to emit the call instructions or any instructions leading up to it.
- chrisseaton 4y agoYeah obviously it's a compiler intrinsic. That's not what I or the person I was replying to was saying, and wasn't the relevant point. We were saying how else could you give the compiler an intrinsic, without a call of some kind? Like what other syntactic mechanism is there you can treat as an intrinsic in the compiler.
- tlb 4y agoSorry, I misread it as wanting a different mechanism. Which I don't think is needed. Some compilers used #pragma for it, but that was ugly and ill-specified, and made it hard to work around missing features by defining your own version.
- a2800276 4y ago> std::unreachable() goes a step further, and tells the compiler that it doesn't > even have to emit the call instructions or any instructions leading up to it. But I don't see a reason why this is not be possible with an attribute? (I realize this is a purely aesthetic call from me .. a function that doesn't actually get called seems out of place). My guess would be that using a function allows niche compilers that don't support attributes to support `unreachable` and is easy to implement for compilers already using __builtin
- planede 4y ago1.) 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<int> 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 corresponding enum. In generic context you might not even know the underlying type. 3.) I absolutely agree. I think it was a mistake to include. The included `std::byteswap` can be expressed in terms of `std::ranges::reverse(std::as_writable_bytes(obj))`. It could be quality of implementation detail to get a bswap instruction from the latter. I would be happy with equivalents of the `htonl` functions in the standard library, but I have strong opinions of the appropriate function signatures of it for C++.
- a2800276 4y ago> 2.) static_cast<int> 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 > corresponding enum. > > In generic context you might not even know the underlying type. Thanks for pointing this out! I'm aware that the underlying type of an enum isn't always `int`. This is what I meant by saying the `static_cast` feels more expressive to me, i.e. reading the code, at least I'll now on sight what type is being used here. I'd probably have to 'let go' more to effectively use CPP, but this category of mechanism to cope with a generic/type system so complicated that I can't figure out the type of anything anymore. In the same vein, 'making code more robust to changes' feels like kicking the can down the road (to me). At some point you'll need to deal with the actual type ...
- gpderetta 4y agoRe 3), all the <bits> 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_writeable_bytes.
- s28l 4y ago1. 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-flow analysis that the compiler will do. Pragmas are processed by the pre-processor, so they aren't appropriate for expressing control flow hints. 2. `std::to_underlying(t)` is a wrapper around `static_cast<std::underlying_type_t<std::remove_cv_t<std::remove_reference_t<decltype(t)>>>>(t)`. So a lot fewer characters. It's also useful since `std::underlying_type_t<T>` behaves weirdly if `T` is not an enum type. I think you are maybe missing the context that C++ allows the representation of an enum to be defined, e.g. `enum class X : unsigned char {};` vs `enum class Y : unsigned long long {};`. So you can't always cast to `int`. Technically, this isn't the case in C either: the type defaults to `int`, but the compiler will pick a larger type if necessary, e.g. `enum Z { a = ((long long)INT_MAX) + 1 };` 3. `htonl` are not standardized, so they were not part of the C++ standard library. Also, on Windows, I believe you'd need to include `winsock.h` to get access to them, which has its own idiosyncratic issues. 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. [0]: https://stackoverflow.com/questions/60802864/emulating-gccs-builtin-unreachable-in-visual-studio https://stackoverflow.com/questions/60802864/emulating-gccs-...
- lelanthran 4y ago> From a compiler perspective, using a function makes the most sense, since that fits into the existing control-flow analysis that the compiler will do. Pragmas are processed by the pre-processor, so they aren't appropriate for expressing control flow hints. This seems par for the course for all C++ stuff: it's designed from a compiler perspective, not from a programmer perspective. The correct way, IMHO, is to design features that supports the user's workflow, not to design the same feature in a way to make the compiler's job easier. > `htonl` are not standardized, so they were not part of the C++ standard library. They're POSIX standardised. The decision should have been to adopt something that exists in an existing and widespread standard rather than the worrisome not-invented-here syndrome that I see here.
- colanderman 4y agoLongtime 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<auto> doesn't make sense to me (that reads like a no-op). static_cast<underlying> introduces a new reserved word which is a big no-no. 3. This is not the same as htonl, which only does anything on little-endian machines. (htonl is also a POSIX function, not a C++ function.)
- lvass 4y ago1. Isn't std inadequate in "embedded" programming?
- tialaramex 4y agoIn 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.
- colanderman 4y agoDepends on what's you're targeting, but if you're coding for say, AVRs, you're not using C++, but rather C, in which case `unreachable` is your friend. [1] [1] https://en.cppreference.com/w/c/program/unreachable https://en.cppreference.com/w/c/program/unreachable
- pjmlp 4y agoIn some cases not even C, just a tiny subset full of chip vendor extensions.
- SubjectToChange 4y agoA lot of it, sure. But there are a lot of header only types which do not dynamically allocate memory on their own. For instance <algorithm>, <atomic>, <bitset>, <limits>, <iterator>, <type_traits>, etc.
- deleted 4y ago[deleted]
- taylorius 4y agoI 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.
- kllrnohj 4y agoThat'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