Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 404
leanid.chaika Avatar asked Nov 21 '18 09:11

leanid.chaika


3 Answers

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.

like image 160
Shafik Yaghmour Avatar answered Nov 04 '22 19:11

Shafik Yaghmour


As of 2021 it's still not fixed, but as a work-around I'm using something like this:

namespace detail {
struct DownloaderHints
{       
    int32_t numOfMaxEasyHandles = 8;
};
}

class Downloader {
public:
    using Hints = details::DownloaderHints;
    static Downloader *Create(const Hints &hints = Hints());
};
like image 22
trozen Avatar answered Nov 04 '22 19:11

trozen


As of 2022 the bug is still not fixed, but yet another workaround is to default a constructor in the .cpp file:

class Downloader
{
public:
    struct Hints
    {       
        int32_t numOfMaxEasyHandles = 8;
        Hints();
    };

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

// In .cpp:

Downloader::Hints::Hints() = default;
like image 32
Mikhail Avatar answered Nov 04 '22 21:11

Mikhail