Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

const T{}; works, const T; fails when T is a non-POD,

To start with, I have a struct with one value with a default value

struct S {
    int a = 1;
};

This type can be default constructed when it is non-const / non-constexpr by both gcc and clang. Under both, std::is_pod<S>::value is false. The weird behavior is as follows:

S s1; // works under both
const S s2{}; // works under both
const S s3; // only works in gcc, clang wants a user-provided constructor

None of the following attempts makes a difference to clang:

struct S {
    int a = 1;
    constexpr S() = default; // defaulted ctor
    virtual void f() { } // virtual function, not an aggregate
  private:
    int b = 2; // private member, really not an aggregate
};

The only thing I can do that makes this work is to add constexpr S() { } explicitly. It seems really wrong to me that const S s; fails while const S s{}; especially when the type is not an aggregate.

The standard makes me think that Clang is right
N4296: 8.5/7

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

So why does gcc allow this, and is S{}; not default initializing, even when the type is not a POD or an aggregate?

like image 421
Ryan Haining Avatar asked Apr 16 '15 18:04

Ryan Haining


2 Answers

const S s3;

Is covered by [dcl.init]/12:

If no initializer is specified for an object, the object is default-initialized.

Thus, as required by your quote, a user-provided default constructor must be present. Adding one like so

struct S {
    int a = 1;
    constexpr S(){}
};

then makes the declaration compile fine.

[..] especially when the type is not an aggregate.

S is an aggregate in your case, and the reason why const S s{} is valid. Aggregate initialization is applied for const S s{}, and everything's fine.
If S is not an aggregate,

List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed (8.5.1).
  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

Now consider the definition of value initialization:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

The default ctor is indeed nontrivial since a member has got an initializer ([class.ctor]/4.9), but that's irrelevant since the constraints are checked eitherway. Hence default-initialization it is, and the line

const S s{};

Is just as valid (or invalid!) as

const S t;

So why does gcc allow this

Well:

  1. Speaking in terms of the current standard, GCC is not compliant; See above.

  2. There is an active CWG issue, #253, created fifteen years ago, that covers a similar scenario. The final note on this one from a 2011 meeting says

    If the implicit default constructor initializes all subobjects, no initializer should be required.

    That is the case with the implicit default constructor for S, and this would make all your lines valid.

  3. GCC developers (e.g. here) implied that since the committee basically agreed upon that above resolution, the current behavior of GCC is feasible and should not be adjusted. So one could well argue that GCC is right and the standard is broken.

like image 78
Columbo Avatar answered Nov 06 '22 08:11

Columbo


So it looks like gcc is basing this on DR 253 even though this is not resolved yet. We can see this from the the following gcc bug report which says:

This is by design, because as DR 253 shows, the normative standard is flawed.

and the gcc change that brought this into effect says:

Core 234 - allow const objects with no initializer or user-provided default constructor if the defaulted constructor initializes all the subobjects.

So technically clang is correct and gcc is not conformant but it seems like they believe DR 253 will be resolved in their favor. This makes complete sense if the main concern is indeterminate initial value which as far as I can tell it is. This change is documented in gcc 4.6 release notes:

In 4.6.0 and 4.6.1 G++ no longer allows objects of const-qualified type to be default initialized unless the type has a user-declared default constructor. In 4.6.2 G++ implements the proposed resolution of DR 253, so default initialization is allowed if it initializes all subobjects. Code that fails to compile can be fixed by providing an initializer e.g.

struct A { A(); };
struct B : A { int i; };
const B b = B();
like image 5
Shafik Yaghmour Avatar answered Nov 06 '22 08:11

Shafik Yaghmour