102

The C++ standard (section 8.5) says:

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

Why? I can't think of any reason why a user-provided constructor is required in this case.

struct B{
  B():x(42){}
  int doSomeStuff() const{return x;}
  int x;
};

struct A{
  A(){}//other than "because the standard says so", why is this line required?

  B b;//not required for this example, just to illustrate
      //how this situation isn't totally useless
};

int main(){
  const A a;
}
Karu
  • 4,512
  • 4
  • 30
  • 31
  • 2
    The line wouldn't seem to be required in your example (see http://ideone.com/qqiXR) because you declared but did not define/initialize `a`, but gcc-4.3.4 accepts it even when you do (see http://ideone.com/uHvFS) – Ray Toal Sep 14 '11 at 05:34
  • 1
    The example above both declares and defines `a`. Comeau produces an error "const variable "a" requires an initializer -- class "A" has no explicitly declared default constructor" if the line is commented out. – Karu Sep 14 '11 at 06:04
  • 1
    http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#253 – Jonathan Wakely Jul 24 '14 at 22:30
  • 5
    This is fixed in C++11, you can write `const A a{}` :) – Howard Lovatt Jul 28 '15 at 02:00

5 Answers5

69

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:

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


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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    I believe your last example is a compile error - `nonPOD_B` doesn't have a user-provided default constructor so the line `const nonPOD_B b2` isn't allowed. – Karu Sep 14 '11 at 05:40
  • 1
    Another way of making the class a non-POD is by giving it a data member which isn't a POD (eg. my struct `B` in the question). But the user-provided default constructor is still required in that case. – Karu Sep 14 '11 at 05:41
  • "If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor." – Karu Sep 14 '11 at 05:48
  • @Karu: I've read that. It seems there are other passages in the spec, which allows `const` non-POD object to get initialized by calling default-constructor generated by compiler. – Nawaz Sep 14 '11 at 05:51
  • Then there is a contradiction in the standard. I think it is more likely that GCC 4.3.4 isn't perfect. – Karu Sep 14 '11 at 05:57
  • Comeau produces an error for that last example:"ComeauTest.c", line 7: error: const variable "b2" requires an initializer -- class "nonPOD_B" has no explicitly declared default constructor const nonPOD_B b2; //initialized – Karu Sep 14 '11 at 06:01
  • @Karu: Now I also feel GCC 4.3.4 has bug. Let me explore it a bit more. – Nawaz Sep 14 '11 at 06:02
  • 8.5/9 doesn't say anything about PODs, and so applies equally to non-POD types. – Karu Sep 14 '11 at 06:34
  • clang 3.1 has a bug in which it doesn't recognize that a virtual function makes a class non-POD for this purpose. – Omnifarious Jan 24 '13 at 23:06
  • you are wrong, p1 should be zero initialized as a static object. [2003: 8.5/6]: Every object of static storage duration shall be zero-initialized at program startup before any other initialization takes place. – ZAB Feb 18 '13 at 10:58
  • also there was an issue http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#78 but it was misunderstood. now standard clearly say that static const objects should be zero initialized constants. – ZAB Feb 18 '13 at 11:00
  • @ZAB: It is assumed that the object is declared as local variable to a function, otherwise how can one write `p1.i = 10;` at the namespace level in which case `p1` will be statically initialized to zero. I'm aware of this case, and have discussed it in detail here : [What is dynamic intialization of object in c++?](http://stackoverflow.com/questions/5945897/what-is-dynamic-intialization-of-object-in-c) – Nawaz Feb 18 '13 at 11:19
  • 2
    Your ideone links seem to be broken, and it would be great if this answer could be updated to C++11/14 because §8.5 doesn't mention POD at all. – Oktalist Nov 15 '14 at 13:50
  • @Nawaz: If I am not wrong POD p2 = POD(); does value initialization that ultimately results in zero initialization of variable i. – Destructor Jul 04 '15 at 16:13
12

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.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • But by adding `A(){}`, the error will go away, so it doesn't prevent anything. The rule doesn't work recursively - `X(){}` is never needed for that example. – Karu Sep 14 '11 at 05:32
  • 2
    Well, at least by forcing the programmer to add a constructor, he is forced to give a minute's thought to the problem and maybe come up with a non-trivial one – arne Sep 14 '11 at 05:40
  • @Karu I only addressed half the question -- fixed that :) – Luc Danton Sep 14 '11 at 05:46
  • 4
    @arne: The only problem is that it's the wrong programmer. The person trying to instantiate the class might give all the thought he wants to the matter, but they might not be able to modify the class. The class author thought about the members, saw that they were all sensibly initialised by the implicit default constructor, so never added one. – Karu Sep 14 '11 at 05:52
  • 3
    What I've taken from this part of the standard is "always always declare a default constructor for non-POD types, in case someone wants to make a const instance one day". That seems a bit overkill. – Karu Sep 14 '11 at 05:54
  • @Karu Is it so bad to pay the costs of initialization some of the time, e.g. `const A a {};` (`const A a = A();` for C++03)? I'd sooner do that than add a constructor. – Luc Danton Sep 14 '11 at 05:57
  • @Karu: Yeah, that's true. I hadn't thought about that. – arne Sep 14 '11 at 05:57
  • @Luc Danton: Maybe it's not so bad for quick-to-construct classes, but it's still a bit messy having an unnecessary copy just to get around a sentence in the standard that doesn't look like it has any justification. – Karu Sep 14 '11 at 06:06
  • @Karu There is no copy with `const A a {};` (it's value initialization). – Luc Danton Sep 14 '11 at 06:10
  • @Luc Danton: Sorry, I didn't parse your parentheses correctly :) Still, I'm only thinking about C++03 for this question, and it would be nice if the braces weren't required for C++0x anyway. – Karu Sep 14 '11 at 06:36
11

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).

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
David Stone
  • 26,872
  • 14
  • 68
  • 84
  • 3
    But this still forbids `struct A { int n; A() = default; }; const A a;` while allowing `struct B { int n; B() {} }; const B b;` because the new wording still says "user-provided" not "user-declared" and I'm left scratching my head why the committee chose to exclude explicitly-defaulted default constructors from this DR, forcing us to make our classes non-trivial if we want const objects with uninitialized members. – Oktalist Nov 20 '17 at 22:31
  • 1
    Interesting, but there's still an edge-case that I've run into. With `MyPOD` being a POD `struct`, `static MyPOD x;` -- relying on zero-initialization (is that the right one?) to set the member variable(s) appropriately -- compiles, but `static const MyPOD x;` does not. Is there any chance that _that_ will get fixed? – Joshua Green Feb 24 '18 at 03:38
4

I was watching Timur Doumler's talk at Meeting C++ 2018 and I finally realised why the standard requires a user-provided constructor here, not merely a user-declared one. It has to do with the rules for value initialisation.

Consider two classes: A has a user-declared constructor, B has a user-provided constructor:

struct A {
    int x;
    A() = default;
};
struct B {
    int x;
    B() {}
};

At first glance, you might think these two constructors will behave the same. But see how value initialisation behaves differently, while only default initialisation behaves the same:

  • A a; is default initialisation: the member int x is uninitialised.
  • B b; is default initialisation: the member int x is uninitialised.
  • A a{}; is value initialisation: the member int x is zero-initialised.
  • B b{}; is value initialisation: the member int x is uninitialised.

Now see what happens when we add const:

  • const A a; is default initialisation: this is ill-formed due to the rule quoted in the question.
  • const B b; is default initialisation: the member int x is uninitialised.
  • const A a{}; is value initialisation: the member int x is zero-initialised.
  • const B b{}; is value initialisation: the member int x is uninitialised.

An uninitialised const scalar (e.g. the int x member) would be useless: writing to it is ill-formed (because it's const) and reading from it is UB (because it holds an indeterminate value). So this rule prevents you from creating such a thing, by forcing you to either add an initialiser or opt-in to the dangerous behaviour by adding a user-provided constructor.

I think it would be nice to have an attribute like [[uninitialized]] to tell the compiler when you're intentionally not initialising an object. Then we wouldn't be forced to make our class not trivially default constructible to get around this corner case. This attribute has actually been proposed, but just like all the other standard attributes, it does not mandate any normative behaviour, being merely a hint to the compiler.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
1

Congratulations, you've invented a case in which there need not be any user defined constructor for the const declaration with no initializer to make sense.

Now can you come up with a reasonable re-wording of the rule that covers your case but still makes the cases that should be illegal illegal? Is it less than 5 or 6 paragraphs? Is it easy and obvious how it should be applied in any situation?

I posit that coming up with a rule that allows the declaration you created to make sense is really hard, and making sure that the rule can be applied in a way that makes sense to people when reading code is even harder. I would prefer a somewhat restrictive rule that was the right thing to do in most cases to a very nuanced and complex rule that was difficult to understand and apply.

The question is, is there a compelling reason the rule should be more complex? Is there some code that would otherwise be very difficult to write or understand that can be written much more simply if the rule is more complex?

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • 1
    Here's my suggested wording: "If a program calls for the default initialization of an object of a const-qualified type T, T shall be a non-POD class type.". This would make `const POD x;` illegal just as `const int x;` is illegal (which makes sense, because this is useless for a POD), but make `const NonPOD x;` legal (which makes sense, because it could have subobjects containing useful constructors/destructors, or have a useful constructor/destructor itself). – Karu Sep 15 '11 at 03:35
  • @Karu - That wording might work. I'm used to RFC standardsese, and so feel that 'T shall be' should read 'T must be'. But yes, that could work. – Omnifarious Sep 17 '11 at 21:11
  • @Karu - What about struct NonPod { int i; virtual void f() {} }? It do not make sense to make const NonPod x; legal. – gruzovator Oct 11 '13 at 06:53
  • 1
    @gruzovator Would it make any more sense if you had an empty user-declared default constructor? My suggestion just attempts to remove a pointless requirement of the standard; with or without it there are still infinitely many ways to write code that doesn't make sense. – Karu Oct 14 '13 at 01:20
  • 1
    @Karu I agree with you. Because of this rule in standard there are many classes that have to have user-defined **empty** constructor. I like gcc behaviour. It allows e.g. `struct NonPod { std::string s; }; const NonPod x;` And gives an error when NonPod is `struct NonPod { int i; std::string s; }; const NonPod x;` – gruzovator Oct 15 '13 at 05:03