I’d probably write a tiny wrapper class for each:
template <class T>
class read_only {
T volatile *addr;
public:
read_only(int address) : addr((T *)address) {}
operator T() volatile const { return *addr; }
};
template <class T>
class write_only {
T volatile *addr;
public:
write_only(int address) : addr ((T *)address) {}
// chaining not allowed since it's write only.
void operator=(T const &t) volatile { *addr = t; }
};
At least assuming your system has a reasonable compiler, I’d expect both of these to be optimized so the generated code was indistinguishable from using a raw pointer. Usage:
read_only<unsigned char> x(0x1234);
write_only<unsigned char> y(0x1235);
y = x + 1; // No problem
x = y; // won't compile