In the answer to
it was shown that a program could have undefined behavior "depending on" (des Pudels Kern in this question) how an implementation used implementation leeway given by the standard. As an example, [expr.prim.lambda.closure]/2:
The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [...] The closure type is not an aggregate type. An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:
- (2.1) the size and/or alignment of the closure type,
- (2.2) whether the closure type is trivially copyable ([class.prop]), or
- (2.3) whether the closure type is a standard-layout class ([class.prop]). [...]
It was pointed out in a comment to the answer that this scenario is not implementation-defined behavior
"implementation-defined" has a very specific meaning ([intro.abstract]/2); this isn't a case of that.
Would a program which had undefined behavior (UB) conditionally on such implementation leeway, have unconditional UB, possibly as per [intro.abstract]/5? Or how would such a program be described, in standardese terms?
Assuming I understand the question correctly, here is a simpler example:
void* storage = ::operator new(100);
new (storage) std::string;
In some language implementation, where the string fits in the memory, the behaviour of this example program would be defined. But the standard does not provide a guarantee that any language implementation satisfies that assumption and in language implementation where the assumption doesn't hold, the behaviour is undefined.
The behaviour is undefined conditionally, depending on the language implementation. Same applies to the more subtle example described in the question.
It's not "implementation defined" behaviour because the standard doesn't say that it's "implementation defined" using those quoted words. If standard did say that, it would imply that language implementation must document that behaviour. As it is, there is no requirement to document whether closure type is trivially copyable.
To avoid this phrase with special meaning, we can use alternatives such as "implementation dependent" or "unspecified" to describe the situation instead.
If you wish to write programs that are portable to any language implementation of the current standard, including one's that exist in the future whose implementation you cannot know at the moment, you should not unconditionally rely on such implementation details.
You could use a type trait to observe whether the closure is trivially copyable, and conditionally use std::bit_cast only when it is well formed and well defined - if you have a good reason to do so.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With