leanid.chaika
leanid.chaika

Reputation: 2442

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.

Upvotes: 44

Views: 7211

Answers (5)

Ryan Tarpine
Ryan Tarpine

Reputation: 791

The year is 2024 and this still isn't fixed, but here's another workaround -- overload the function instead of having a default argument:

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

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

Upvotes: 0

szym
szym

Reputation: 5846

Here's my workaround:

class Downloader
{
public:
    struct Hints
    {       
        int32_t numOfMaxEasyHandles = 8;
    };
    static Hints default_hints() { return {}; }
    static Downloader *Create(const Hints &hints = default_hints());
};

alternatively:

...
    struct Hints
    {       
        int32_t numOfMaxEasyHandles = 8;
        static Hints Default() { return {}; }
    };
    static Downloader *Create(const Hints &hints = Hints::Default());
...

Here it is on godbolt. This has the advantage of keeping Hints an aggregate, so we can still write:

return Downloader::Create({
  .numOfMaxEasyHandles = 24,
});

Upvotes: 11

Mikhail
Mikhail

Reputation: 21769

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;

Note however, that this prevents Hints from being an aggregate, which might e.g. prevent usage of dedicated initalizers.

Upvotes: 7

trozen
trozen

Reputation: 1273

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

Upvotes: 11

Shafik Yaghmour
Shafik Yaghmour

Reputation: 158499

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.

Upvotes: 28

Related Questions