Why does C++ require a user-provided default constructor to default-construct a const object?

The reason is that if the class doesn't have a user-defined constructor, then it can be POD, and the POD class is not initialized by default. So if you declare a const object of POD which is uninitialized, what use of it? So I think the Standard enforces this rule so that the object can actually be useful.

struct POD
{
  int i;
};

POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!

POD p2 = POD(); //initialized

const POD p3 = POD(); //initialized 

const POD p4; //uninitialized  - error - as we cannot change it later on!

But if you make the class a non-POD:

struct nonPOD_A
{
    nonPOD_A() {} //this makes non-POD
};

nonPOD_A a1; //initialized 
const nonPOD_A a2; //initialized 

Note the difference between POD and non-POD.

User-defined constructor is one way to make the class non-POD. There are several ways you can do that.

struct nonPOD_B
{
    virtual void f() {} //virtual function make it non-POD
};

nonPOD_B b1; //initialized 
const nonPOD_B b2; //initialized 

Notice nonPOD_B doesn't defined user-defined constructor. Compile it. It will compile:

  • http://www.ideone.com/h7TsA

And comment the virtual function, then it gives error, as expected:

  • http://www.ideone.com/SWk7B

Well, I think, you misunderstood the passage. It first says this (§8.5/9):

If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; [...]

It talks about non-POD class possibly cv-qualified type. That is, the non-POD object shall be default-initialized if there is no initializer specified. And what is default-initialized? For non-POD, the spec says (§8.5/5),

To default-initialize an object of type T means:
— if T is a non-POD class type (clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

It simply talks about default constructor of T, whether its user-defined or compiler-generated is irrelevant.

If you're clear up to this, then understand what the spec next says ((§8.5/9),

[...]; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor.

So this text implies, the program will be ill-formed if the object is of const-qualified POD type, and there is no initializer specified (because POD are not default initialized):

POD p1; //uninitialized - can be useful - hence allowed
const POD p2; //uninitialized - never useful  - hence not allowed - error

By the way, this compiles fine, because its non-POD, and can be default-initialized.


Pure speculation on my part, but consider that other types have a similar restriction, too:

int main()
{
    const int i; // invalid
}

So not only is this rule consistent, but it also (recursively) prevents unitialized const (sub)objects:

struct X {
    int j;
};
struct A {
    int i;
    X x;
}

int main()
{
    const A a; // a.i and a.x.j in unitialized states!
}

As for the other side of the question (allowing it for types with a default constructor), I think the idea is that a type with a user-provided default constructor is supposed to always be in some sensible state after construction. Note that the rules as they are allow for the following:

struct A {
    explicit
    A(int i): initialized(true), i(i) {} // valued constructor

    A(): initialized(false) {}

    bool initialized;
    int i;
};

const A a; // class invariant set up for the object
           // yet we didn't pay the cost of initializing a.i

Then perhaps we could formulate a rule like 'at least one member must be sensibly initialized in a user-provided default constructor', but that's way too much time spent trying to protect against Murphy. C++ tends to trust the programmer on certain points.


This was considered a defect (against all versions of the standard) and it was resolved by Core Working Group (CWG) Defect 253. The new wording for the standard states in http://eel.is/c++draft/dcl.init#7

A class type T is const-default-constructible if default-initialization of T would invoke a user-provided constructor of T (not inherited from a base class) or if

  • each direct non-variant non-static data member M of T has a default member initializer or, if M is of class type X (or array thereof), X is const-default-constructible,
  • if T is a union with at least one non-static data member, exactly one variant member has a default member initializer,
  • if T is not a union, for each anonymous union member with at least one non-static data member (if any), exactly one non-static data member has a default member initializer, and
  • each potentially constructed base class of T is const-default-constructible.

If a program calls for the default-initialization of an object of a const-qualified type T, T shall be a const-default-constructible class type or array thereof.

This wording essentially means that the obvious code works. If you initialize all of your bases and members, you can say A const a; regardless of how or if you spell any constructors.

struct A {
};
A const a;

gcc has accepted this since 4.6.4. clang has accepted this since 3.9.0. Visual Studio also accepts this (at least in 2017, not sure if sooner).

Tags:

C++