You are quoting in comment an answer to this similar question but I'm more convinced by the other answer.
The most upvoted answer is citing https://eel.is/c++draft/basic.lval#11 stating that type-accessibility is a necessary condition in order to access the stored value of an object but it is not a sufficient one.
In comment I added https://eel.is/c++draft/expr.static.cast#12 but you would need pointer-interconvertibility which is not achieved if you want to modify a value from its byte storage (see P1839).
In the linked answer above, Jan Schultke clearly explain that there is no guarantee that you can modify p from buf and he even goes farther pointing P1839#non-goals, stating that modification of an object through fiddling with its object representation as an array of bytes is not covered in the standard. Only reading is considered and P1839 aims at making it straight.
But if your goal is to do such a fiddling, there is a workaround through std::bit_cast:
#include <bit>
#include <cstddef>
#include <cstdint>
#include <type_traits>
template <typename T>
concept trivially_copyable = std::is_trivially_copyable_v<T>;
namespace details {
// required to pass a plain array to and from bit_cast
template <typename T, std::size_t N>
struct wrap_array {
T arr[N];
};
} // namespace details
template <trivially_copyable T>
constexpr void set_byte(T& obj, std::size_t pos, std::byte val) {
details::wrap_array<std::byte, sizeof(T)> obj_rep;
// static only with c++>=23
// static details::wrap_array<std::byte,sizeof(T)> obj_rep;
obj_rep = std::bit_cast<details::wrap_array<std::byte, sizeof(T)>, T>(obj);
if (pos >= sizeof(T)) {
return;
}
obj_rep.arr[pos] = val;
obj = std::bit_cast<T, details::wrap_array<std::byte, sizeof(T)>>(obj_rep);
}
template <trivially_copyable T>
[[nodiscard]] constexpr T set_byte(T const& obj, std::size_t pos,
std::byte val) {
details::wrap_array<std::byte, sizeof(T)> obj_rep;
// static only with c++>=23
// static details::wrap_array<std::byte,sizeof(T)> obj_rep;
obj_rep = std::bit_cast<details::wrap_array<std::byte, sizeof(T)>, T>(obj);
if (pos >= sizeof(T)) {
return obj;
}
obj_rep.arr[pos] = val;
return std::bit_cast<T, details::wrap_array<std::byte, sizeof(T)>>(obj_rep);
}
template <trivially_copyable T>
constexpr void set_byte_unsafe(T& obj, std::size_t pos, std::byte val) {
std::byte* ptr = reinterpret_cast<std::byte*>(&obj);
if (pos >= sizeof(T)) {
return;
}
ptr[pos] = val;
return;
}
// hides the real value from the compiler
std::uint32_t faucet();
// prevent the compiler from optimizing away a variable
void sink(std::uint32_t const&);
int main() {
std::uint32_t x{0};
x = faucet();
set_byte(x, 1, std::byte{255});
sink(x);
x = faucet();
set_byte_unsafe(x, 2, std::byte{255});
sink(x);
constexpr std::uint32_t y{0};
constexpr auto z = set_byte(y, 1, std::byte{255});
sink(z);
return static_cast<int>(x);
}
LIVE
The non-void set_byte version is only there for having the possibility to initialize a constexpr variable with it.
What is interesting is to compare, on compiler explorer, the assembly produced in a optimized build. It is quite the same for the safe version and the unsafe one, which uses reinterpret_cast and might well be UB for different reasons (see many recent discussions, around your other question).
I am not aware of proposal to modify an object value from its object representation, perhaps because the compilers, so far, behave as expected (bad reason) and because you can do byte fiddling through bit_cast (good reason), hoping for compiler to optimize (bad reason, but not so bad). So your trading a hypothetical UB for a hypothetical miss-optimization.
Side-note about set_byte implementation:
- I am restricting it to trivially copyable types, but I do not see the point of low level value manipulation for more complex types;
- I am wrapping the array of bytes inside a struct as I do not no a way to directly use
std::bit_cast on plain array. But wrap_array object representation is guaranteed to be the same as the wrapped array (and at the same memory location).
char. It would be a rather meaningless permission if changes weren't guaranteed to propagate immediately.unsigned char(as you have on the screenshot), then the array will be able to "provide storage" for the new object (per eel.is/c++draft/intro.object#3), so the new object will be nested in the array (eel.is/c++draft/intro.object#4.2). So while your snippet in the question could be undefined, the one on the screenshot looks legal, because they useunsigned char[].p->xin a register and lose the changed value viabuf[4].reinterpret_cast<unsigned char *>(p)instead of the original array, IMO.) The wording regarding object representation has some nice defects, so while it can be interesting to dig into, I doubt it's worth the time.Sobject is still in its lifetime (no call todelete). Onlyplifetime has ended.