17

Consider:

#include <string>
#include <iostream>

class Foo
{
     public:
         Foo( char const * msg ) : x( y ) 
         {
             y = msg;
         }

         std::string const & x;

     private:
         std::string y;
};

int main( int argc, char * argv[] )
{
    if ( argc >= 2 )
    {
        Foo f( argv[1] );
        std::cout << f.x << std::endl;
    }
}

This compiles and prints the first parameter... but I have doubts whether it is actually "legal" / well-formed. I know that the initializer list should initialize variables in order of their declaration in the class, lest you reference variables that haven't been initialized yet. But what about member variables not in the initializer list? Can I safely create references to them as showcased?

(The example is, of course, meaningless. It's just to clarify what I am talking about.)

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • 2
    legal as long as you don't read `x` before `y` is initialized. – Jarod42 Apr 25 '18 at 10:57
  • Rather than doing `y = msg` in the constructor body, it would normally be better to add `y(msg)` in the initialiser list. – Peter Apr 25 '18 at 11:00
  • @Peter: "The real code" retrieves `y` in a way that it's not possible to put it in the initializer list (through a call to a C API that requires some setting up). If I could initialize `y` in the initializer list (and thus make it `const`), I wouldn't *need* `x`. ;-) Actually I don't really **need** `x` at all, the construct just made me wonder, and I like to keep my knowledge of C++ as complete as possible. ;-) – DevSolar Apr 25 '18 at 11:03
  • How "meaningless" is the example? I pulled those standard quotes based solely off of it :\ – StoryTeller - Unslander Monica Apr 25 '18 at 11:14
  • @DevSolar I'm not convinced that setting up for a C API call would prevent initializing `y` in the member initializer list. Hint: Use a function. – eerorika Apr 25 '18 at 11:16
  • @StoryTeller: It's an attempt to have "the real code" represented in a [mcve]. As I said to Peter, the "real" `y` comes from a C API -- which I have to initialize, create a hande, get a struct from a function call *using* that handle, and initialize `y` from a union within that struct, which might be of one type or another depending on what kind of device is connected... I *could perhaps* squeeze all that into the initializer list, but that would become more "tricky" than I am willing to maintain later on. ;-) – DevSolar Apr 25 '18 at 11:18
  • 1
    You can put all that C code into a static member function that returns a string and then initialize your const string by calling that function. Any failure is handled by throwing an exception from the function. – Khouri Giordano Apr 25 '18 at 11:21
  • 2
    Can we stop trying to find an XY problem here? Yes there are probably other ways I *could* approach the underlying problem, but I wanted to know *explicitly* if I can set up a reference this way, because I was not sure, and might **have** to do it at some point. – DevSolar Apr 25 '18 at 11:24

3 Answers3

22

You can do so1 because:

  1. x and y are both in scope already ([basic.scope.class]/1).
  2. Since you are obtaining a reference after the constructor started executing ([class.cdtor]/1) and storage for y is obtained already ([basic.life]/7), that reference can be bound to y.

Using that reference inside the constructor's compound statement (after member initialization is all over) is also fine. That is because y is considered initialized, and x refers now to an object whose lifetime has started.


1 - There's a caveat for language lawyers. Technically, a reference needs to be bound to a valid object ([dcl.ref]/5), meaning one whose lifetime has started. However, like Core Language issue 363 details, it's expected to work! The problematic wording and a possible resolution is discussed in Core Language issue 453 (courtesy of @T.C. in a deleted comment). There's a bug in the standard, but your code is intended to be well formed, and implementations are generally aware of it.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Comments have been wiped than I answer aided by my defected memory...You're right THIS (what you describe in your answer) is the _sane_ behavior (no doubt, that's why I cited your answer as explanation), what I'm saying is that Standard left too much room for interpretation (and I find new wording wordy and too much convoluted by I'm not a LL). Because of that each (sane) implementation was free to interpret it literally or _cum grano salis_. If each implementation is free to do it then it renders this _implementation defined_... – Adriano Repetti Apr 26 '18 at 08:42
  • @AdrianoRepetti - I'm not really up for reopening the discussion. I added an extra note (shame T.C.'s comment was deleted), so language lawyers should now be satisfied as well. – StoryTeller - Unslander Monica Apr 26 '18 at 08:56
  • ...which is reasonable, IMO, especially because Standard does not mandate how references are implemented (then to distinguish about _valid object_ and _valid storage_ isn't possible); I suppose (just my guess) it is the reason new wording is so cryptic. – Adriano Repetti Apr 26 '18 at 08:58
  • yes, there isn't much left to discuss, I think, but yes, that small note is just what I was looking for! Now I can delete my answer. – Adriano Repetti Apr 26 '18 at 08:59
3

But what about member variables not in the initializer list?

Whether the variables are in the initializer list or not, is irrelevant in this regard. If a variable is not in initializer list (nor has a default member initializer), then it is default initialized.

y is initialized after x. This is not because of the member initializer list, because the member initializer list does not affect the order of initialization of members. Members are initialized in the order of their declaration.

However, whether y is initialized or not is also irrelevant. It is well formed to bind a reference to to a member before the member is initialized (except binding to a virtual base of uninitialized member; that would have UB).


In regard to safety (or perhaps correctness more accurately), I recommend that you take some time to consider what happens when Foo is copied. What will x refer to? Is that what the user of the class would expect?

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Can I safely create references to them as showcased?

Yes, you can. The storage address of the member y is known regardless initialized it or not, so x(y) reference initialization is legal.

273K
  • 29,503
  • 10
  • 41
  • 64