3
$\begingroup$

More on the topic of type-punning being problematic:

C23 added constexpr and new rules as to how compile-time constants can be created and handled. We can now access members of structures and unions as compile-time constants, provided said structures and unions are compile-time constants themselves:

constexpr struct {
    int x;
} s = {10};
switch (some_var) {
    case s.x:
        // ...
}
static_assert(s.x == 10);
static int x = s.x;
// All of the above should be valid

However there is one notable exception:

If the member-access operator . accesses a member of a union constant, the accessed member shall be the same as the member that is initialized by the union constant's initializer.

This essentially has the effect of preventing compile-time type puns. In addition to preventing determining characters in a string at compile-time but that is story for another time.

But what is the rationale for this? What prevents the language from simply defining the behavior to yield the same value that would be yielded at runtime? Why would a compile-time type-pun be anymore problematic than a runtime type-pun? Why would a language allow one but not the other?

$\endgroup$
6
  • $\begingroup$ Does the value yielded at runtime depend on the architecture of the computer running it? I could imagine inconsistent results if you accessed the bytes of an integer as big-endian at compile-time but little-endian at runtime, for example. $\endgroup$
    – kaya3
    Commented Mar 22 at 4:55
  • 1
    $\begingroup$ IOW, would make a cross-compiler more difficult to implement (maybe). $\endgroup$
    – Pablo H
    Commented Mar 22 at 13:28
  • 1
    $\begingroup$ @kaya3 I find it hard to imagine how 'little-endian at runtime but big-endian at compile-time' could be, for example, the compiler has to know ahead of time what endian to put integer constants in the executable. $\endgroup$
    – CPlus
    Commented Mar 22 at 16:02
  • $\begingroup$ some discussion in the paper that proposed them open-std.org/jtc1/sc22/wg14/www/docs/… $\endgroup$
    – Moonchild
    Commented Mar 22 at 19:04
  • 3
    $\begingroup$ @user38 That could get kind of complicated. You wouldn't be able to just execute it like normal instructions. In general, you'd almost need some kind of lightweight "virtual machine". Or some form of virtualization, anyway $\endgroup$ Commented Mar 22 at 19:25

1 Answer 1

3
$\begingroup$

Disclaimer: Unfortunately, due to C being an ISO standard, the exact reason(s) may only have briefly existed orally within the room where the committee met, and we are most likely stuck with (educated) guesses.

I'd venture Conservatism.

First of all, it should be noted that C++ doesn't allow access to any other union member than the active member (no type puning in C++), therefore there's no pressure from C++ to enable type puning at compile time in C.

Secondly, it should be noted that it's easier to allow later, than it is to restrict later: starting by allowing, then later restricting, breaks backward compatibility.

Therefore, a prudent committee would do well to NOT allow type puning at first, and only re-evaluate whether to allow type puning if sufficient evidence is presented that it would be useful in the real world.


I find it hard to imagine how 'little-endian at runtime but big-endian at compile-time' could be, for example, the compiler has to know ahead of time what endian to put integer constants in the executable.

Most modern compilers are split between front-end and back-end, Clang being an archetypal example here.

Compile-time execution is a front-end feature (the Clang front-end for example), whereas lowering a constant to the appropriate format for the execution target is a back-end feature (the LLVM back-end for example).

The compiler as a whole may therefore know how to lower a constant to a specific endianness, while the front-end part of the compiler in charge of compile-time execution may not.

This goes a bit further than just endianness though -- endianness is, in the end, trivial. While integers are typically fairly regular -- especially now that 2-complement is mandated -- floating points can still be... weird. qNaN, sNaN, not quite IEEE-754 compliant representations, etc... Therefore, type-puning from integer to floating point -- or vice-versa -- could be significantly more involved for cross-compilers.

It is definitely doable, but is it worth it?

The committee seems to have decided that, at the very least, it's not worth it yet.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .