Below has 3 different styles for indirect accessing. I am trying to understand if they all have well-defined behavior, and can be safely used for cross-platform code.
#include <cstdint>
#include <iostream>
#include <any>
using namespace std;
#if 1
#define INLINE [[gnu::always_inline]] inline
#else
#define INLINE [[gnu::noinline]]
#endif
INLINE
void mod1(void *p) {
*static_cast<int*>(p) = 10;
}
INLINE
void mod2(uintptr_t p) {
*reinterpret_cast<int*>(p) = 12;
}
INLINE
void mod3(any p) {
*any_cast<int*>(p) = 14;
}
int test1() {
int a1 = 5;
mod1(&a1);
return a1;
}
int test2() {
int a2 = 6;
mod2(reinterpret_cast<uintptr_t>(&a2));
return a2;
}
int test3() {
int a3 = 6;
mod3(&a3);
return a3;
}
int main() {
cout << test1() << "\n";
cout << test2() << "\n";
cout << test3() << "\n";
}
I tried inline and noinline, they all work as expected, output 10 12 14 so I think inlining does not matter here.
Are they all well-defined behavior by C++ standard? Any help would be greatly appreciated :)
[Edit] Even if we consider type-based alias analysis.
Technically the behavior of neither of these is specified by the standard, because you are using implementation-defined attributes which could have any effect. However practically speaking these attributes are irrelevant to the behavior here.
That aside:
test1
is correct and specified to work as expected. Casting an object pointer to (possibly cv-qualified) void*
and back to the original type via static_cast
or reinterpret_cast
is always allowed and yields the original pointer value. However this round-trip through void*
to the same type is the only sequence of such casts that has this guarantee in general.
test2
is conditionally-supported. It is not guaranteed by the C++ standard that std::uintptr_t
or any other type to which object pointers can be converted exists. If std::uintptr_t
does exist, then the behavior is well-defined with the expected output. The mapping between integral and object pointer types is implementation-defined except that round-trip casting back to the original pointer type through an integral type of sufficient size yields the original pointer value.
test3
is well-defined to give the expected result, except that it may (in contrast to the other two cases) fail by throwing std::bad_alloc
. The whole point of std::any
is that you can put any type (e.g. int*
) into it and access it again in a type-safe manner with std::any_cast
. It also requires RTTI which is sometimes disabled on purpose (in non-conformance to the standard).