I stumbled over some code that appears to be correct.
It is supposed to provide a public, immutable pointer, while keeping the modifiable non-const pointer private.
Strangely enough, this code broke on SN C++ Compiler (for PlayStation 3), but worked fine on GCC. On SN C++, data would point to bogus values, while m_data would work as intended.
Code in question:
#include <cstdint>
class Foo
{
public:
Foo() : data((const std::uint8_t* const&)m_data)
{
m_data = nullptr; // Set later in some other member function.
}
const std::uint8_t* const &data;
private:
std::uint8_t* m_data;
};
Does this code invoke undefined behavior? As far as I know, casting to a reference like that will convert (const std::uint8_t* const&)m_data into *reinterpret_cast<const std::uint8_t* const*>(&m_data).
Test cases:
Foo* new_foo() { return new Foo; }
and looking at the generated disassembly. Note that it is PowerPC 64-bit with 32-bit longs and pointers.
SN C++: ps3ppusnc -o test-sn.o -O3 -c test.cpp
0000000000000000 <._Z7new_foov>:
0: f8 21 ff 81 stdu r1,-128(r1) # ffffff80
4: 7c 08 02 a6 mflr r0
8: f8 01 00 90 std r0,144(r1) # 90
c: fb e1 00 78 std r31,120(r1) # 78
10: 38 60 00 08 li r3,8
14: 3b e0 00 00 li r31,0
18: 48 00 00 01 bl 18 <._Z7new_foov+0x18>
1c: 60 00 00 00 nop
20: 2c 03 00 00 cmpwi r3,0
24: 41 82 00 38 beq 5c <._Z7new_foov+0x5c>
28: 30 81 00 70 addic r4,r1,112 # 70
2c: 93 e3 00 04 stw r31,4(r3) <-- Set m_data to r31 (0).
30: 60 7f 00 00 ori r31,r3,0
34: 90 83 00 00 stw r4,0(r3) <-- Set data to r4 (r1 + 112 (On stack)?!)
38: 63 e3 00 00 ori r3,r31,0
3c: e8 01 00 90 ld r0,144(r1) # 90
40: 7c 08 03 a6 mtlr r0
44: eb e1 00 78 ld r31,120(r1) # 78
48: 38 21 00 80 addi r1,r1,128 # 80
4c: 4e 80 00 20 blr
GCC 4.1.1: ppu-lv2-g++ -o test-gcc.o -O3 -c test.cpp
0000000000000000 <._Z7new_foov>:
0: 38 60 00 08 li r3,8
4: 7c 08 02 a6 mflr r0
8: f8 21 ff 91 stdu r1,-112(r1) # ffffff90
c: f8 01 00 80 std r0,128(r1) # 80
10: 48 00 00 01 bl 10 <._Z7new_foov+0x10>
14: 60 00 00 00 nop
18: 7c 69 1b 78 mr r9,r3
1c: 38 00 00 00 li r0,0
20: 39 63 00 04 addi r11,r3,4 <-- Compute address of m_data
24: 78 63 00 20 clrldi r3,r3,32 # 20
28: 90 09 00 04 stw r0,4(r9) <-- Set m_data to r0 (0).
2c: e8 01 00 80 ld r0,128(r1) # 80
30: 38 21 00 70 addi r1,r1,112 # 70
34: 91 69 00 00 stw r11,0(r9) <-- Set data reference to m_data.
38: 7c 08 03 a6 mtlr r0
3c: 4e 80 00 20 blr
You’ll have to fight through the spec language in 4.4/4 (of C++11), but I believe that 3.10/10 permits this. It says that object may be aliased as “a type similar to the dynamic type of the object”.
In this case, the dynamic type of the object is
std::uint8_t*, and the similar type isconst std::uint8_t* const. I think. Check 4.4/4 for yourself.[Update: C++03 doesn’t mention “similar” types in 3.10/15, so it may be that you’re in trouble on C++03, which presumably is what SNC works with.]
There’s a second thing that needs to be checked, which is whether it’s OK to initialize the reference
databy binding it to an object that hasn’t been initialized yet (m_data). Intuitively that seems OK, since the reference to the uninitializedm_datais never converted to an rvalue. It’s easily fixed, anyway.