5 ms·
It's interesting to see that the complexity of c++ is so great that even the code in standards proposals contains undefined behaviour. The no_destroy class trig
by martijntje 3y ago
It's interesting to see that the complexity of c++ is so great that even the code in standards proposals contains undefined behaviour. The no_destroy class triggers UB when .get() is called, because it fails to launder the returned pointer. This is required since c++17.
- pjmlp 3y agoThe way C++ is going is no longer the language I felt in love as my next programming tool after Object Pascal (Turbo/Delphi). C++26 is going to introduce erroneous behavior. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2795r4.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p27...
- maccard 3y agoThis is absolute madness, and IMO goes against everything c++ should stand for. It's a non-zero runtime overhead breaking language change, for a case that should be handled by disallowing uninitialized reads, with an opt in for undefined behaviour.
- pjmlp 3y agoThe madness is the increasing complexity, adding features into the standard without having them being tested on field (like in any other sane language), last meeting had 210 people voting in for their special features, trying to fix safety without cleaning out the features that make it impossible in first place, and so on. As for non-zero runtime overhead, that is long gone, since stuff like std::map, std::regexp and refusal from compiler vendors to break an ABI that isn't even defined on the ISO level, yet it blocks any proposal that has the side effect of breaking it.
- maccard 3y ago> adding features into the standard without having them being tested on field Could not agree more. Modules are a perfect example of this. > As for non-zero runtime overhead, that is long gone, since stuff like std::map, std::regexp I have a little sympathy for this - they're libraries that are replaceable (yet you'd think we'd have learned from map, regex, unique_ptr that getting it right first time is impossible, and _maybe_ we should start putting these things in the language), and the overhead there is opt-in. In this case, if I have the following code: char buf[SOME_VERY_LARGE_CONSTANT]; bool result = fill_buf_from_c_library(buf, sizeof(buf) / sizeof(buf[0])); assert(result); // we've guaranteed at runtime that this memory is initialized now comes with a significant performance hit with a compiler upgrade, and requires code changes to fix > refusal from compiler vendors to break an ABI that isn't even defined on the ISO level, yet it blocks any proposal that has the side effect of breaking it. And yet this is true at the same time. Madness.
- tialaramex 3y ago> I have a little sympathy for this - they're libraries that are replaceable In effect unlike the C stdlib, where sure - there's almost nothing in the freestanding library, the C++ stdlib is full of higher level features even in freestanding, more like Rust's core. It's not as rich as Rust's core, but it's definitely in that direction. So that means there's a responsibility for Quality of Implementation on those features, they're not really "replaceable" because they're in the fundamental standard library even on bare metal which means a "replacement" is just a parallel implementation. That's probably fine to some extent for std::unordered_map, but it's very silly for std::function, and I'd argue it's even silly for std::unique_ptr and std::string. > now comes with a significant performance hit with a compiler upgrade, and requires code changes to fix It's a problem that it took C++ until now to fix this, but that's still on them. There is a proposal (which I'd guess you'll hate) to have a Rust-style uninitialized wrapper type so that you can write what you meant here and it's clear to the compiler and to future human maintainers that those ain't chars, those are a kiss of death until after that C function successfully does what it says it does. The C definition of assert, which C++ inherits, is problematic here, because it's a no-op in release builds.
- maccard 3y ago> So that means there's a responsibility for Quality of Implementation on those features, they're not really "replaceable" because they're in the fundamental standard library even on bare metal which means a "replacement" is just a parallel implementation. That's probably fine to some extent for std::unordered_map, but it's very silly for std::function, and I'd argue it's even silly for std::unique_ptr and std::string. I think we agree here - you can replace std::unordered_map with absl::map etc, so the impact is lower. I do wish we'd stop doing it, though - see fmt. I agree that it's incredibly stupid for std::function and std::unique_ptr to not be language level features - unique_ptr is the poster child for something that could faster and safer with less compile time overhead if implemented in the language rather than in the library. > There is a proposal (which I'd guess you'll hate) No, I think that's great actually, but I don't want that _and_ noinit/indeterminite (which is proposed in the link above) _and_ a new category of behaviour. I want one option, and I want failure of adherence to it to either be compiler enforced, implelentation defined, or Undefined Behaviour, _not_ "erroneus behaviour". > The C definition of assert, which C++ inherits, is problematic here, because it's a no-op in release builds. Sorry, yeah you're totally right. my day to day C++ work has a custom set of assertions that in release builds are basically the following (simplified for posting here) - #define our_assert(conditional) \ if (!conditional) { \ log_stuff_and_flush() \ std::abort() \ } > because it's a no-op in release builds. This falls into the same group of back compat as the unwillingness to break ABI IMO - you can have an optimised build without NDEBUG, or an unoptimised build with NDEBUG, and the side effects of disabling bounds checking is turning assert into a noop.
- Asooka 3y agoOh thank God! I have been asking for something like "erroneous behaviour" for years. Clang in particular is very fond of deleting safety checks if they check for conditions which it thinks are only possible under undefined behaviour. Every year I have to fix one or two bugs that stem from code from completely separate modules interacting in a way that manages to produce a state which leads to undefined behaviour. The option to opt-in to UB is also a very good idea - we can turn it on in the hot parts of code and restore performance where it really matters, while constraining the impact, i.e. even if the code has UB, that cannot poison the rest of the program. I hope this renewed focus on safety will lead to the default for what is currently undefined behaviour being more in line with what was originally intended - sort-of arbitrary behaviour dependent on the platform's state at the time of code execution. As in, less constrained than "implementation defined", since there is no guarantee it will produce the same result every time, but more constrained than UB, since it does not invalidate the program.
- lelanthran 3y agoI fell out of love with C++ over a decade ago. The discipline required to not shoot your foot off in a non trivial project was far greater than than the extra code needed when writing plain C, or, for bigger projects, C# and Java. I still reach for C first, and if I need higher level abstractions, I'm reaching for something other than C++.
- geertj 3y agoAre you sure that with https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p05... this is still undefined behavior?
- martijntje 3y agoIf I read that paper correctly, that's only valid if the object can be constructed without code being run - i.e. a trivially constructible type. Since there is no limitation on the no_destroy type, you can create any type with it, including those which have a non-trivial constructor. Of course, you could use a concept to restrain it to only trivially constructible types, or you could omit the launder in an if constexpr branch and still be compliant.
- protomolecule 3y agoI don't think that get() needs to launder that pointer. The object of type T is constructed only once in the storage provided by an array of bytes.
- LegionMammal978 3y agoIt doesn't really matter how many times the T is constructed: the pointer resulting from "new" points to the T object, but the pointer from accessing the member just points to the byte array, and the latter cannot be turned into the former without std::launder().
- protomolecule 3y agoI don't think you are right here. std::launder addresses a specific situation: when an object is replaced by another object of the same type but compiler doesn't know that the first object's lifetime has ended and the same address points to a different object. In this case compiler may still use cached values of constant or reference fields of the dead object or its vptr. In this case the byte array merely provides storage for an object of type T created by the placement new operator and that object is never replaced by anything. The lifetime of the byte array itself doesn't end when a nested object is created.
- LegionMammal978 3y agoThat's one of the situations for std::launder(). (However, in most cases where the new object has exactly the same type, C++20 has made laundering unnecessary with its 'transparently replaceable' criterion.) Yet the other situation is precisely this one [0] [1]. The byte array does provide storage for the T, but a pointer to the byte array is not a pointer to the T. You need to launder the pointer to get from the former to the latter. Such are the rules when you're bound as strictly to type-based aliasing as standard C++ is. (Not that any real compiler is nearly as strict about byte arrays in practice: if they were, then many Unix networking APIs would be practically unusable. Indeed, people care about it so little that Linux is happily adding new syscalls that write data into variable-length buffers and expect the user to arbitrarily cast them back into structs.) [0] https://en.cppreference.com/w/cpp/utility/launder#Notes https://en.cppreference.com/w/cpp/utility/launder#Notes [1] https://stackoverflow.com/a/39382728 https://stackoverflow.com/a/39382728