I have a struct
that needs to have some instances declared volatile
because they represent memory that is shared with a driver (i.e. the memory may be changed by a process outside my C++ program). The struct
also needs to be trivially-copyable, because I will be sharing instances of it with some code that requires all its inputs to be trivially-copyable. These two requirements seem to mean that it is impossible for me to safely assign a new value to volatile
instances of the struct
.
Here's a simplified example of what I'm trying to do:
struct foo {
uint16_t a;
uint16_t b;
};
int main() {
static_assert(std::is_trivially_copyable<foo>::value, "Oh no!");
volatile foo vfoo;
foo foo_value{10, 20};
vfoo = foo_value;
}
If I try to compile this with g++, it fails on the line vfoo = foo_value
with "Error: passing 'volatile foo' as 'this' argument discards qualifiers." According to this question, that's because the implicitly-defined assignment operator is not declared volatile, and I need to define a volatile copy-assignment operator in order to assign to a volatile object. However, if I do this:
struct foo {
uint16_t a;
uint16_t b;
volatile foo& operator=(const foo& f) volatile {
if(this != &f) {
a = f.a;
b = f.b;
}
return *this;
}
}
Then the static assertion fails, because foo
is no longer trivially-copyable if it has a user-defined assignment operator.
Since the compiler has apparently decided that I am not allowed to do this very simple action, I'm currently using this workaround:
int main() {
static_assert(std::is_trivially_copyable<foo>::value, "Oh no!");
volatile foo vfoo;
foo foo_value{10, 20};
memcpy(const_cast<foo*>(&vfoo), &foo_value, sizeof(foo));
std::atomic_signal_fence(std::memory_order_acq_rel);
}
Obviously, casting away the volatile
isn't a good idea, because that means the compiler is now allowed to violate the semantics volatile
was supposed to enforce (i.e. every read and write in code is translated to an actual read or write to memory). I tried to mitigate this by replacing the assignment with a memcpy
, which should mean the compiler can't optimize away the write (even if it thinks that the write will not be visible to the rest of the program), and by adding a memory fence after the assignment, which should mean the compiler can't choose to delay the write until much later.
Is this the best I can do? Is there a better workaround that will get closer to the correct semantics for volatile
? Or is there a way to get the compiler to let me assign a new value to a volatile
struct without making the struct non-trivially-copyable?
If you don't necessarily need to use the assignment operator (i.e. if you consider the memcpy alternative viable) then you could write a non-operator assignment function instead:
volatile foo& volatile_assign(volatile foo& f, const foo& o) {
if(&f != &o) {
f.a = o.a;
f.b = o.b;
}
return f;
}
You can use a member function if you so prefer.
I've written this based on your example, but consider whether the self assignment check is valid regarding volatile semantics. Shouldn't the same values be re-written? I don't think the case is even valid unless the object is actually non-volatile since otherwise we would be reading a volatile object through the non-volatile reference (maybe you need volatile qualification for the other operand as well).