For this discussion, I assume that scalar objects are things like ints, floats, chars, bools, and pointers. Non-scalar objects are aggregates (structs) that are made up of scalar types and aggregates recursively.
Given this assumption, do C++ programs ever access aggregates distinctly from their scalar components?
As an example:
struct s { int a; float b; };
void assign1(s& out, s const& in) { out = in; }
void assign2(s& out, s const& in) { out.a = in.a; out.b = in.b; }
Clearly assign1
and assign2
are equivalent in practice, and both access the int s::a
and float s::b
. But do either of them also access the whole aggregate in any sense?
The interpretation that only scalar objects are ever actually accessed has interesting consequences.
For instance, according to the resolution of my other question here, forming a reference to an object does not constitute access. Given that resolution, I can write a function like this:
void assign3(s& out, s const& in) {
int& a_out = out.a; // no access
int const& a_in = in.a; // no access
a_out = a_in; // access some ints
}
No "access" occurs except on the third line, which accesses some ints. Whether out
and in
actually refer to objects of s
type is inconsequential. Only a_out
and a_in
must actually refer to ints.
Given this, and the fact that an object's address is the address of its first non-static data member, I am within my rights to write
int out, const in = 42;
assign3(reinterpret_cast<s&>(out), reinterpret_cast<s const&>(in));
If all of these assumptions hold, then C and C++ are for the most part just portable assembly language and aliasing rules are just to help the compiler correctly read registers out of the x87 floating point coprocessor.
Of course the assumptions don't hold. I'm wrong. But why am I wrong? Why do the standard documents have all these rules about effective type or dynamic type?
Given struct a { int a; }; struct b { int b; };
what benefit is there to making access of a::a
through a b::b
undefined except in some limited situations involving unions?