Alex Rivera | Logout

Initializing mutually-referencing objects

Asked 2012-01-03T23:07:33.837
19

Consider the following pair of mutually referencing types:

struct A;
struct B { A& a; };
struct A { B& b; };

This can be initialized with aggregate initialization in GCC, Clang, Intel, MSVC, but not SunPro which insists that user-defined ctors are required.

struct {A first; B second;} pair = {pair.second, pair.first};

Is this initialization legal?

slightly more elaborate demo: http://ideone.com/P4XFw

Now, heeding Sun's warning, what about classes with user-defined constructors? The following works in GCC, clang, Intel, SunPro, and MSVC, but is it legal?

struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };

struct {B first; A second;} pair = {pair.second, pair.first};

demo: http://ideone.com/QQEpA

And finally, what if the container is not trivial either, e.g. (works in G++, Intel, Clang (with warnings), but not MSVC ("pair" unknown in initializer) or SunPro ("pair is not a structure")

std::pair<A, B> pair(pair.second, pair.first);

From what I can see, §3.8[basic.life]/6 forbids access to a non-static data member before lifetime begins, but is lvalue evaluation of pair.second "access" to second? If it is, then are all three initializations illegal? Also, §8.3.2[dcl.ref]/5 says "reference shall be initialized to refer to a valid object" which probably makes all three illegal as well, but perhaps I'm missing something and the compilers accept this for a reason.

PS: I realize these classes are not practical in any way, hence the language-lawyer tag. Related and marginally more practical old discussion here: c++ language-lawyer

Edit
Report

1 Answer

1

From compiler point of view references are nothing else but const pointers. Rewrite your example with pointers and it becomes clear how and why it works:

struct A;
struct B { A* a; };
struct A { B* b; };
struct {A first; B second;} pair = {&(pair.second), &(pair.first)}; //parentheses for clarity

As Schollii wrote: memory is allocated beforehand, thus addressable. There is no access nor evaluation because of references/pointers. That's merely taking addresses of "second" and "first", simple pointer arithmetics.

I could rant about how using references in any place other than operator is language abuse, but I think this example highlights the issue well enough :)

(From now on I write all the ctors manually. Your compiler may or may not do this automagically for you.) Try using new:

struct A;
struct B { A& a; B(A& arg):a(arg){;} };
struct A { B& b; A(B& arg):b(arg){;} };
typedef struct PAIR{A first; B second; PAIR(B& argB, A& argA):first(argB),second(argA){;}} *PPAIR, *const CPPAIR;
PPAIR pPair = NULL;// just to clean garbage or 0xCDCD
pPair = new PAIR(pPair->second, pPair->first);

Now it depends on order of execution. If assignment is made last (after ctor) the second.p will point to 0x0000 and first.ref to e.g. 0x0004.
Actually, http://codepad.org/yp911ug6 here it's the ctors which are run last (makes most sense!), therefore everything works (even though it appears it shouldn't).

Can't speak about templates, though.

But your question was "Is that legal?". No law forbids it.
Will it work? Well, I don't trust compiler makers enough to make any statements about that.

answered 2012-01-14T08:45:35.230

Your Answer