Home > Net >  Why does default-constructibility behave weirdly for inner structs with NSDMI?
Why does default-constructibility behave weirdly for inner structs with NSDMI?

Time:09-17

Consider the following code:

#include <type_traits>

struct outer {
    struct inner {
        unsigned int x = 0;
    };
//    static_assert(std::is_default_constructible<inner>::value,
//          "not default ctorable - inside");
};

static_assert(std::is_default_constructible<outer::inner>::value,
    "not default ctorable - outside");

This compiles fine. But - if I uncomment the static assert inside outer - both asserts fail with clang and gcc . Why should they not both pass?

Notes:

  • The inner class is, in fact, complete, at the point of the first static assertion. The assertion does not fail due to incompleteness (a failure which produces a specific error message about incompleteness).

  • Unlike in the related Why is my class non default-constructible? , here - there are no templates, so there is no instantiation-before- the-definition.

  • If you remove the initializer of x and enable the assertions, the code compiles. (This is also unlike the related question.)

  • This:

    #include <type_traits>
    
    struct outer {
         struct inner {
             unsigned int x = 0;
         };
    
         inner get_an_inner() { 
             static_assert(std::is_default_constructible<outer::inner>::value,
                 "not default ctorable - outside");
             return inner{}; 
         }
    };
    

    compiles!

  • If we add an explicit default constructor, constexpr inner() {} - the program compiles.

  • There's a LLVM bug report about basically the same thing, but it's not just clang and the bug report has not gotten any comments.

Updates:

  • Have submitted GCC bug 102199 (against libstdc ).
  • Have commented on the LLVM bug, let's see what happens there.

CodePudding user response:

The completeness status of member classes is complicated. Because their member functions are allowed to do forward lookup into the surrounding class scope, those member functions are treated as not yet being defined when processing the (rest of) the containing class, despite the fact that the most basic notion of completeness is plainly satisfied:

struct A {
  struct B {};
  B b;          // OK
};

There are various open issues in this general area: CWG2335 and CWG1360, for instance.

CodePudding user response:

First, why the inner static assert causes the outer static assert to fail? That's because templates are instantiated only once. Once std::is_default_constructible<inner>::value is false, it will remain false, even if default constructibility somehow changes. So the outer static_assert is not very interesting. Why does the inner static_assert fail?

In order to understand this, let's simplify this to the bare minimum. No templates or standard library!

struct outer {
    struct inner {
        unsigned int x = 0;
    };

    static constexpr inner i{};
};

This fails with a mysterious compiler message.

$ g   -c dc.cpp 
dc.cpp:6:30: error: default member initializer for ‘outer::inner::x’ required before the end of its enclosing class
    8 |     static constexpr inner i{};
      |                              ^
dc.cpp:3:24: note: defined here
    5 |         unsigned int x = 0;
      |                        ^~~~


$ clang    -c dc.cpp 
dc.cpp:6:30: error: default member initializer for 'x' needed within definition of enclosing class 'outer' outside of member functions
    static constexpr inner i{};
                             ^
dc.cpp:3:22: note: default member initializer declared here
        unsigned int x = 0;
                     ^
1 error generated.

So the compiler indeed cannot default-construct inner where needed, but why?

A quick search on the text of the error message yields this. The accepted answer classes it as a gcc and clang bug, but I'm not convinced. The clang bug report says "Then we would only reject the cases where there's an actual dependency cycle", but the standard doesn't seem to require dependency cycle detection anywhere. This could be a defect in the standard rather than a compiler bug.

  • Related