\n
I came to this question while pondering why libc++'s std::variant
doesn't use [[no_unique_address]]
for its state, meaning I can't use it in a struct that needs to be layed out efficiently into another and can do better with an open-coded union and tag. It's not clear to me whether [[no_unique_address]]
could be safely used in std::variant
assuming we don't care about an ABI change. But I'm interested in the wider question of what the downsides are in general.
Reputation: 6638
[[no_unique_address]]
allows structs to be more efficiently laid out, by sometimes putting members into tail padding of preceding members. We all like to save RAM and have more cache-efficient structs. So why not use it everywhere, all the time?
With reference to the C++ standard and/or the Itanium C++ ABI, what reasons are there to avoid using [[no_unique_address]]
on most or all members?
For example:
Are there safety issues with this, like concerns about overwriting the object in the tail padding when using operator=
? This answer implies maybe yes, but it's not clear whether it's a bug in the implementation of std::copy
and also I can't reproduce it with modern clang.
I haven't found anything in the standard that says operator=
is liable to cause a problem here. Also it seems like any problem with [[no_unique_address]]
would also apply to base classes into whose tail padding subclass members are placed.
Are there efficiency concerns, like maybe requiring more code to load or store values? Given that the alignment is still correct I can't see why this would be the case.
I came to this question while pondering why libc++'s std::variant
doesn't use [[no_unique_address]]
for its state, meaning I can't use it in a struct that needs to be layed out efficiently into another and can do better with an open-coded union and tag. It's not clear to me whether [[no_unique_address]]
could be safely used in std::variant
assuming we don't care about an ABI change. But I'm interested in the wider question of what the downsides are in general.
Upvotes: 10
Views: 262
Reputation: 797
There are definitely cases where the behavior of a well defined program can become unspecified by adding [[no_unique_address]]
. A somewhat contrived example I could come up with:
#include <functional>
#include <iostream>
struct X {
[[no_unique_address]] std::greater<> g;
[[no_unique_address]] std::less<> l;
};
void
foo(auto const& comp1, auto const& comp2) {
if(static_cast<void const*>(&comp1) == static_cast<void const*>(&comp2)) {
std::cout << comp1(10, 5);
} else {
std::cout << comp1(10, 5) << comp2(5, 10);
}
}
int main()
{
X x;
foo(x.l, x.g);
}
We're creating a struct with two different comparsion functors and passing them as parameters to a function. This function checks first if they're equal by comparing their address and if so only executes one, otherwise both.
With [[no_unique_address]]
it's unspecified which branch this program will execute. But without, you can be guaranteed that it will execute the else branch.
So making [[no_unique_address]]
the default might have broken some old code.
Upvotes: 5