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

5

43

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.

Fiddlehead answered 21/11, 2018 at 9:32 Comment(4)
This seems to be related: #43819814 and this one: #46867186Belier
seems clang still struggles from that bug !!Guild
@Belier they look related but not exactly matching bugs, I found another bug report which seems to match pretty close to this case.Latishalatitude
To make GCC and Clang diverge here, one can declare noexcept = default constructor in Hints and default argument Create(const Hints &hints = {}), demo: gcc.godbolt.org/z/GGjKf34zvOtway
L
28

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.

Latishalatitude answered 22/11, 2018 at 4:26 Comment(1)
I decide to add this bug to gcc too: gcc.gnu.org/bugzilla/show_bug.cgi?id=88165 (can't find match, so add new one)Fiddlehead
A
11

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());
};
Annunciation answered 21/5, 2021 at 12:58 Comment(1)
FTR, still nothing in 2023 May (according to a quick test with the latest GCC and CLANG at godbolt.org).Kemp
W
9

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,
});
Wordplay answered 9/3, 2023 at 23:44 Comment(1)
Thanks, I found this to be the best workaround, since I really wanted to maintain support for designated initialization of the inner struct.Loveinidleness
S
7

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.

Submaxillary answered 5/8, 2022 at 10:47 Comment(1)
The downside here is that Hints is no longer an aggregate so cannot use designated initializers.Wordplay
H
0

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);
};
Hydrate answered 14/5, 2024 at 15:14 Comment(1)
Thanks. This issue awaits Committee decision: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1890Otway

© 2022 - 2025 — McMap. All rights reserved.