Попытайтесь понять сообщение об ошибке компилятора: инициализатор члена по умолчанию требуется до конца его окружающего класса

Я пытаюсь следующий код с тремя компиляторами (msvc2017, gcc8.2, clang7.0) и msvc2017 работает полностью, но gcc и clang нет. Я хочу понять, что не так с моим кодом, и почему компилятор не может его скомпилировать.

#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;
}

Вы можете поиграть с этим кодом самостоятельно на https://wandbox.org/ и увидеть ошибку:

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;
                                     ^~~~

Почему gcc и clang не скомпилируют этот код даже с uncomment Hints() = default? Мои команды компиляции:

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

Но если я раскомментирую Hints(){} все три компилятора будут работать. Может быть, это ошибка компилятора? Заранее спасибо.

Ответ 1

Это ошибка clang и gcc, у нас есть отчет об ошибке clang: инициализатор члена по умолчанию для 'm', необходимый в определении включающего класса для аргумента функции по умолчанию, который имеет следующий пример:

#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.);
}

который производит следующую похожую диагностику:

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();
                   ^

Ричард Смит указывает, что это ошибка:

Что касается комментария № 0: если мы хотим исправить это раз и навсегда, мы должны использовать тот же метод, который мы используем для отложенного синтаксического анализа шаблона: научить Sema перезванивать в синтаксический анализатор для анализа отложенных областей по требованию. Тогда мы бы только отвергли случаи, когда существует реальный цикл зависимости.

Хотя не объясняет почему в деталях.