0

The Standard tells us1 in an example2 than the following is legal:

struct T1 { int a, b; };
struct T2 { int c; double d; };
union U { T1 t1; T2 t2; };
int f() {
  U u = { { 1, 2 } };   // active member is t1
  return u.t2.c;        // OK, as if u.t1.a were nominated
}

Now, adding an indirection level (member function) and removing another (union), I wonder:

Is the following well-defined?

struct T1      { int a; int value() { return a; }};
struct T2 : T1 { int b; };
int f() {
  T1 t1 = { 1 };
  return reinterpret_cast<T2*>(&t1)->value();
}

I know the cast is not undefined behavior3 by itself; but is the -> operator on its result infringing [basic.lval]/114? or are we saved by [expr.ref]/45?

I want to think it is legal since t1.a and reinterpret_cast<T2*>(&t1)->a have the same address6 (hence the same offset in their representation). But is it?


1) [class.mem]/22 and [class.mem]/23 define what layout-compatible classes are; [class.mem]/25 gives us:

In a standard-layout union with an active member of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.

2) In [class.mem]/25.

3) [expr.reinterpret.cast]/7:

An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_­cast<cv T*>(static_­cast<cv void*>(v)).

and [expr.static.cast]/13

A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

4) [basic.lval]/11

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

  • [ rules not applicable ]
  • (11.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
  • [ rules not applicable ]

5) [expr.ref]/4 on postfix expression involving the notation E1.E2 or E1->E2.

6) [class.mem]/26

If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member if that member is not a bit-field. Its address is also the same as the address of each of its base class subobjects.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • 2
    I've put a lot of effort in this question. If you downvote it, please tell me why so I can improve it. – YSC Nov 30 '18 at 14:43
  • I'm going to tentatively say that this question is a dupe of https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – Rakete1111 Dec 01 '18 at 07:19

1 Answers1

2

No, that's UB as far as I can tell.

I don't see how [expr.ref]p4 can save you. You are accessing an object of type T1 through a pointer of a different type (which is also not the dynamic type of the object).

Also, [class.mem]p26 is not relevant as you are already violating [basic.lval]p11.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162