C++26 is going to introduce erroneous behavior.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p27...
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.
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.
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.
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.
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.
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++.
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.
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.
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.)