c++constructorlanguage-lawyerdefault-arguments

Object with default argument constructor can be declared inside another class with not yet declared variable as constructor parameter


I tried to google my question, but given the title I find it hard to formulate properly, so I wasn't able to find anything. So I decided to ask here: why does this compile?

#include <cstdio>

class Test
{
  public:
    Test(int param = 42)
    {
      printf("ctor %d\n", param);
      _data = param;
    }
    
  private:
    int _data = 0;
};

class Test2
{
  // wtf? v is not yet declared
  Test t{ v };
  int v = 333;
};

int main()
{
  Test2 t2;

  // This doesn't compile though.  
  // Test t{ v };
  // int v = 333;

  printf("don't optimize 0x%X\n", &t2);
  
  return 0;
}

I expected the compiler to issue an error of "undeclared variable v" or something.

Also, Test constructor always prints 0 even if I declare v const.

Of course, if I rearrange lines like this:

int v = 333;
Test t{ v };

then it works as expected.


Solution

  • As pointed to by @RichardCritten in comment, class scope says (emphasis are mine):

    The potential scope of a name declared in a class begins at the point of declaration and includes the rest of the class body, all the derived classes bodies, the function bodies (even if defined outside the class definition or before the declaration of the name), function default arguments, function exception specifications, in-class brace-or-equal initializers, and all these things in nested classes, recursively.

    In your case, it's a "in-class brace-or-equal initializers":

    class Test2
    {
      // wtf? v is not yet declared
      Test t{ v };   // <= This is a in-class brace-or-equal initializers
      int v = 333;
    };
    

    A compiler probably has to do two passes in order to process this information.


    Also, Test constructor always prints 0 even if I declare v const.

    As for why you are getting zero printed, this is due the order of initialization. All members are initialized in order of declaration - this is the golden rule (no matter whether you are using initializers in the class definition or in a constructor). In your case, t is initialized before v.

    This makes your code contain undefined behavior, since you try to use the value of v before it is initialized.

    ... even if I declare v const

    const doesn't change anything in this case. A const non-static member is still a non-static member. You have to make it constexpr static or static const to make a difference.