3

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?

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Maybe "**unspecified** whether or not it has UB"? – HolyBlackCat Aug 19 '21 at 16:02
  • I'm not sure I understand the issue. UB means "undefined behaviour". If a behaviour depends on something outside of the standard, then this literally means that it is undefined in the standard, doesn't it? – freakish Aug 19 '21 at 16:06
  • @freakish: Some compiler writers interpret the fact that the Standard fails to mandate that *all* implementations define a behavior as implying a judgment that no implementations should do so. – supercat Aug 25 '21 at 15:04

2 Answers2

4

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Neither the C nor C++ Standard was written to fully describe all situations where implementations for various platforms and purposes should or should not be expected to process programs meaningfully. The term "implementation defined" is used only in situations where all implementations would be required to specify a behavior which, at least for code running on a single thread, would be consistent with sequential program execution. Even if most implementations should process a construct identically, the Standards will still use the term "Undefined Behavior" if there might be some implementations where it would be impractical to specify and implement a behavior that would always be predictable and consistent with sequential program execution. This among other things applies to constructs that could trap with side effects not anticipated by the Standard. For example, given something like:

float x,y; // Assume at least one might not get written before the following:

float temp= x*y;
if (func1())
  func2(temp);

if no further use is made of temp, an implementation might sensibly defer the multiplication across the function call. If an attempt to multiply an invalid float value might trap, however, the effect of such deferral might be observable. Because implementations that can offer useful useful behavioral guarantees in cases not mandated by the Standard are always free to do so whether or not the Standard would require such behavior, the question of whether to mandate the behavior was only expected to be relevant in cases where it would be impractical implementations to process it meaningfully.

supercat
  • 77,689
  • 9
  • 166
  • 211