Do I really need to implement user-provided constructor for const objects?

前端 未结 4 1954
粉色の甜心
粉色の甜心 2020-11-27 18:45

I have the code:

class A {
  public:
    A() = default;

  private:
    int i = 1;
};

int main() {
  const A a;
  return 0;
}

It compiles

相关标签:
4条回答
  • 2020-11-27 19:17

    Edit: The following is based on outdated information. I just went through N3797 and this is what I found:

    § 8.5/7 [dcl.init]
    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.

    Note the standard quote in the link below says user-declared.


    The following program compiles in g++ but not clang++:

    struct A {};
    
    void f()
    {
      A const a;
    }
    

    And it might be related to this bug report where it was "fixed". g++ fails to compile it once it contains data members unless they're initialized. Note that int member = 1 will no longer make A a POD. Comparatively, clang++ rejects all permutations (empty classes and data members initialized or not.) For an interpretation of what the standard means by the following paragraph:

    § 8.5/9 [dcl.init] says:

    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; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for an object, the object and its subobjects, if any, have an indeterminate initial value; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.

    See Why does C++ require a user-provided default constructor to default-construct a const object?. Supposedly the program is ill-formed if the object is of const-qualified POD type, and there is no initializer specified (because POD are not default initialized). Note how g++ behaves for the following:

    struct A {int a;};
    struct B {int a = 1;};
    int main() 
    {
        A a;
        B b;
        const A c; // A is POD, error
        const B d; // B is not POD, contains data member initializer, no error
    }
    
    0 讨论(0)
  • 2020-11-27 19:25

    Note that you can turn your class easily into one which has a user-defined default constructor:

    class A {
      public:
        A();
    
      private:
        int i = 1;
    };
    
    inline A::A() = default;
    

    According to 8.4.2 [dcl.fct.def.default] paragraph 4:

    ... A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. ...

    This implicitly states that a function which is not explicitly defaulted on its first declaration is not user-provided. In combination with 8.5 [dcl.init] paragraph 6

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

    it seems clear that you cannot use a default constructor defaulted on its first declaration to initialize a const object. However, you can use a defaulted definition if it isn't the first declaration as is done in the code above.

    0 讨论(0)
  • 2020-11-27 19:28

    N3797 §8.5/7 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.

    There's no further example or explanation of this. I agree it seems pretty bizarre. Furthermore the rule was updated in C++11 to be more restrictive than it was in C++03, when class types needed user-declared constructors. (Your constructor is user-declared.)

    The workaround is be to ask for value initialization using {}, or use Dietmar's clever out-of-class inline definition.

    GCC does provide a diagnosis (and quite a nice one, referring to the newer C++11 requirements) if you add another member without an initializer.

      private:
        int i = 1;
        int j;
    

     

    unmem.cpp:11:11: error: uninitialized const ‘a’ [-fpermissive]
       const A a;
               ^
    unmem.cpp:1:7: note: ‘const class A’ has no user-provided default constructor
     class A {
           ^
    unmem.cpp:3:5: note: constructor is not user-provided because it is explicitly defaulted in the class body
         A() = default;
         ^
    unmem.cpp:7:9: note: and the implicitly-defined constructor does not initialize ‘int A::j’
         int j;
    

    The GCC source refers to DR 253, Why must empty or fully-initialized const objects be initialized? This is an open issue in the standard, last updated in August 2011 (post-C++11) with this note:

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

    Therefore whereas Clang complies with C++11 (and will comply as-is with C++14), GCC is implementing the latest thinking of the standardization committee.

    Filed a GCC bug. I predict that you'll need -pedantic to get a diagnosis when (and if) the bug is fixed.

    0 讨论(0)
  • 2020-11-27 19:31

    Since C++17, this code is correct, as is the similar code from this question:

    struct MyClass1 { int i{}; };
    struct MyClass2 { const MyClass1 m; };
    MyClass2 a;
    

    clang 8.0.0 rejects this latter code even with -std=c++17 which means that clang 8.0.0 has a bug.

    In C++17 the following new text was added as [dcl.init]/7 (as per P0490R0 in response to DR 253):

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


    Prior to C++17 there was no such text; an object defined as const must either have an initializer or a user-provided constructor. So, prior to C++17, clang was correct and g++ was bugged to accept the code without diagnostic.

    0 讨论(0)
提交回复
热议问题