First of all, what is a “self-referencing type”? A type (struct or class) whose instance contains one or more references to the instance of the same type.
A sketch:
1 2 3 4 5 |
// Self Referencing type struct SR { char state; const SR & ref; }; |
Why would anybody do this?
This is another question. After I implement this, I might give some valid use cases. But it turns out it is surprisingly non-trivial to implement this simple type in C++20. Hint: unlike pointers, for references one does not need to free the memory pointers are pointing to.
First (we all know, do we not?) one has to initialize the references as type (class or struct ) members. Trying :
SR{}
As expected returns a compiler error response:
error: reference member of type 'const SR &' is uninitialized
I do oblige and create the next version
1 2 3 4 |
struct SR { char state{} ; const SR & ref{} ; }; |
Trying to instantiate this struct, produces a somewhat cryptic message, at first glance:
default member initializer for 'ref' needed within the definition of enclosing class 'SR' outside of member functions
Much later it turned out this message was a clue leading to the solution. clang++ is known for its good and clear error messages. But this one is a definite head-scratcher.
Also, it has to be said, Visual Studio IDE has produced another but a bit clearer message (which is coming from the cl.exe
used by it):
the generated default constructor cannot be used in an
initializer for its own data member
Ok, that would be funny, but perhaps we need to initialize the ref member in a good old pre-modern c++98 way? Let’s try and do the next version.
1 2 3 4 5 |
struct SR { char state{} ; const SR & ref{} ; SR () : ref( /* what to put here? */) {} }; |
Just to immediately realize the chicken-and-egg situation. To initialize the ref member we need to give it a reference to SR. But we are constructing the SR. So we need a reference to non-constructed SR? Or some such thing.
If ref
would be a pointer that would be a nullptr
. But the ref is a reference, not a pointer. Outside of this problem context, this is very clear and easy to understand. Reference cannot exist without being initialized.
1 2 3 4 |
// not a valid c++ --> const int & iref ; // reference must be initialized immediately upon // declaration const int & iref = 42 ; |
We surely all know this. But how to do this in the context of self-referencing type like SR
here, is?
We need to initialize the SR reference member with the default SR instance before one exists.
Not to prolong this “essay” too much and to give you some useful C++ code let me jump to the end of the journey.
After trying to decipher that cryptic clang++ message and googling for an hour or two, or more, I realized I could initialize the ref with the instance of SR with the help of C++ “forward declaration” AND the ‘extern’ keyword.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// forward declare SR struct SR ; // forwad declare extern instance // of SR // declaration that uses extern and has // no initializer is not a definition! extern SR null_sr ; // struct SR { const SR & ref{ // use the extern-aly declaredinstance // not fully instantiated here null_sr } ; char state{} ; }; // here finalize the definition aka instance // for the extern declaration above SR null_sr{} ; |
Almost immediately after this, I found the relevant page on cppreference.com
about reference members. I should have looked into that before. It contains this solution too.
I have to admit clang++ message is cryptic but it gave me a vital clue indeed. After I wasted several hours.
And as it turns out, this issue was not standardized and solved before C++14.
You might find this example useful: Godbolt: https://godbolt.org/z/jE9f1f4WP .