<p>More on the topic of <a href="https://langdev.stackexchange.com/q/2350/38">type-punning being problematic</a>:</p>
<p>C23 added <code>constexpr</code> 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:</p>
<pre><code>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
</code></pre>
<p>However there is one notable exception:</p>
<blockquote>
<p>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.</p>
</blockquote>
<p>This essentially has the effect of preventing compile-time type puns. In addition to <a href="https://stackoverflow.com/a/78197485/16217248">preventing determining characters in a string at compile-time</a> but that is story for another time.</p>
<p>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?</p>

<types><c><type-conversion><constant-expressions><rationale>
Why is type-punning at compile-time a no-go?
CC BY-SA 4.0
2024-03-22T04:55:02.800Z
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.
kaya3
CC BY-SA 4.0
2024-03-22T13:28:47.083Z
IOW, would make a cross-compiler more difficult to implement (maybe).
Pablo H
CC BY-SA 4.0
2024-03-22T16:02:07.790Z
@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.
CPlus
CC BY-SA 4.0
2024-03-22T19:04:07.297Z
some discussion in the paper that proposed them https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm#aggregate-or-union-types
Moonchild
CC BY-SA 4.0
2024-03-22T19:25:27.230Z
@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
David Young
CC BY-SA 4.0
2024-03-23T18:13:03.257Z
@user38 Crosscompiling for a MIPS micro-controller on x86 PC. Big endian at runtime but little-endian at compile time. Without time punning, all the compilier needs to know is how to convert the values from little endian to big endian when writing them. With it? Well the easiest way would probably be convert to target representation, do the punning, and then convert back to compiler representation (which may not be possible in general, especially with floats), and then do the math on it.
user1937198
<p><em>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.</em></p>
<p>I'd venture <strong>Conservatism</strong>.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<hr />
<blockquote>
<p>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.</p>
</blockquote>
<p>Most modern compilers are split between front-end and back-end, Clang being an archetypal example here.</p>
<p>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).</p>
<p>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.</p>
<p>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.</p>
<p>It is definitely <em>doable</em>, but is it worth it?</p>
<p>The committee seems to have decided that, at the very least, it's not worth it <strong>yet</strong>.</p>

CC BY-SA 4.0
2024-03-25T14:46:38.127Z
2024-03-25T14:46:38.127Z
Matthieu M.
# Why is type-punning at compile-time a no-go?
Posted by: username_0 on 2024-03-22
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?
**Tags:** types c type-conversion constant-expressions rationale
> **Comments:**
> **username_1**: 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.  
> **username_2**: IOW, would make a cross-compiler more difficult to implement (maybe).  
> **username_0**: @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.  
> **username_3**: some discussion in the paper that proposed them https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm#aggregate-or-union-types  
> **username_4**: @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  
> **username_5**: @user38 Crosscompiling for a MIPS micro-controller on x86 PC. Big endian at runtime but little-endian at compile time. Without time punning, all the compilier needs to know is how to convert the values from little endian to big endian when writing them. With it? Well the easiest way would probably be convert to target representation, do the punning, and then convert back to compiler representation (which may not be possible in general, especially with floats), and then do the math on it.  
---
## 1 answers
### Answer by username_6 on 2024-03-25. Score: 3 | Accepted answer
*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**.
---
