Search code examples
c++c++11c++14c++17

Try to understand compiler error message: default member initializer required before the end of its enclosing class


I try next code with three compilers (msvc2017, gcc8.2, clang7.0) and msvc2017 works all the way, but gcc and clang not. I want to understand what is wrong with my code, and why compiler can't compile it.

#include <cassert>
#include <iostream>
#include <cstdlib>

class Downloader
{
public:

    struct Hints
    {       
        int32_t numOfMaxEasyHandles = 8;
        //Hints(){}          // <= if I uncomment this all works gcc+clang+msvc
        //Hints() = default; // <= if I uncomment this neither clang no gcc works (msvc - works)
    };

    static Downloader *Create(const Hints &hints = Hints());
};

Downloader* Downloader::Create(const Hints &hints)
{
    std::cout << hints.numOfMaxEasyHandles << std::endl;
    return nullptr;
}

int main()
{
    return 0;
}

You can play with this code yourself on https://wandbox.org/ and see error:

prog.cc:16:58: error: default member initializer for 'Downloader::Hints::numOfMaxEasyHandles' required before the end of its enclosing class
     static Downloader *Create(const Hints &hints = Hints());
                                                          ^
prog.cc:11:37: note: defined here
         int32_t numOfMaxEasyHandles = 8;
                                     ^~~~

Why gcc and clang not compile this code even with uncomment Hints() = default? My compile commands:

  1. $ g++ prog.cc -std=gnu++2a
  2. $ clang++ prog.cc -std=gnu++2a

But if I uncomment Hints(){} all three compilers works. Maybe it is compiler bug? Thanks in advance.


Solution

  • This is a clang and gcc bug, we have a clang bug report for this: default member initializer for 'm' needed within definition of enclosing class for default argument of function which has the following example:

    #include <limits>
    class A
    {
       public:
          class B
          {
             public:
                explicit B() = default;
                ~B() = default;
    
             private:
                double m = std::numeric_limits<double>::max();
          };
    
       void f(double d, const B &b = B{}) {}
    };
    
    int main()
    {
       A a{};
       a.f(0.);
    }
    

    which produces the following similar diagnostic:

    t.cpp(15,34):  error: default member initializer for 'm' needed within definition of enclosing class 'A' outside of member functions
       void f(double d, const B &b = B{}) {}
                                     ^
    t.cpp(12,20):  note: default member initializer declared here
                double m = std::numeric_limits<double>::max();
                       ^
    

    Richard Smith indicates this is a bug:

    Regarding comment#0: if we want to fix this once-and-for-all, we should use the same technique we use for delayed template parsing: teach Sema to call back into the parser to parse the delayed regions on-demand. Then we would only reject the cases where there's an actual dependency cycle.

    Although does not explain why in details.