C++ Const Constructability

[Inspired by CppQuiz #264]

Take this code snippet:

struct C { int i; };
const C c;
...

It fails to compile in gcc, with:

error: ‘const struct C’ has no user-provided default constructor and the implicitly-defined constructor does not initialize ‘int C::i’

Clang and icc give similar error messages. MSVC does agree to compile it, but somewhat reluctantly:

warning C4269: ‘c’: ‘const’ automatic data initialized with compiler generated default constructor produces unreliable results

If you remove the const qualifier, everything builds fine. What’s the deal?

Rationale

An uninitialized object that is also constant would not be able to be populated with meaningful values later – and so is a strong indication of a coding error. The C++ standard made an exception to its’ usual philosophy and tried to stop this particular bullet from hitting your foot:

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

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 …,

  • if T is not a union …,

Limitations

1. First and most obvious, the compiler does not try to check whether the user provided ctor actually does everything it should, or anything at all. This builds fine:

struct C {
 C() {};
 int i;
};
const C c;

Perhaps the ctor contents could have been checked (most compilers already know enough to generate warnings for uninitialized members), but the current standard doesn’t require it. To appease the compiler, it is enough the user supplies any ctor.

2. Currently there are ways – or rather spec loopholes? – to still use the same compiler-generated constructor for const initialization.

struct C {int i;};
const C c1 = C();
const C c2 {};

– both are considered value initialization, distinct from the default initialization referred in this part of the standard.

3. Somewhat surprisingly, while this fails:

struct C {
C() = default;
int i;
};
const C c;

taking the ‘ = default’ out of the class declaration makes the program valid!

struct C {
  C();
  int i;
};
C::C() = default;
const C c;

While in this toy example the difference seems negligible, typically the ctor implementation does not appear in all translation units that use C’s declaration. Thus, the ctor implementation – and in particular whether it’s default or not – is invisible to the compiler, and the standard does not require it to take decisions based on an implementation it can’t see.

This entry was posted in C++. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s