Consider the following structure with an std::optional containing a type that definitely has a "normal" default constructor.
#include <optional>
#include <string>
struct Foo
{
Foo() = default;
const std::optional<std::string> m_value;
};
bool function()
{
Foo foo;
return bool(foo.m_value);
}
Compiling the following with clang 9 (using the system's default libstdc++, for its gcc 8) gives an unexpected warning:
<source>:6:5: warning: explicitly defaulted default constructor is implicitly deleted [-Wdefaulted-function-deleted]
Foo() = default;
^
<source>:7:38: note: default constructor of 'Foo' is implicitly deleted because field 'm_value' of const-qualified type 'const std::optional<std::string>' (aka 'const optional<basic_string<char> >') would not be initialized
const std::optional<std::string> m_value;
^
There's also hard error for Foo foo; since it uses said deleted constructor.
- Deleting the
Foo() = default;constructor gives the same results. - Replacing it with
Foo() {}works! - Removing all the constructors and initializing
fooasFoo foo{};works! - Explicitly initializing the member as
const std::optional<std::string> m_value{};works! - Removing the
constfrom the member works! (but isn't the same meaning) - Using clang 9 with
-stdlib=libc++works! - Using gcc 8.3 (still with
libstdc++) works!
I've read std::optional - construct empty with {} or std::nullopt? which seems to indicate that the libstdc++ implementation choice of an = default constructor for std::optional is likely to blame. But, in that question, the concern was a matter of efficiency of one approach vs. the other. In this case, it seems like a matter of correctness.
(I suspect that the answer to How can std::chrono::duration::duration() be constexpr? is going to be part of the story here.)
I see the same behaviour on Compiler Explorer: https://gcc.godbolt.org/z/Yj1o5P
Compare simple structures of optional and non-optional std::string (in the non-working configurations):
struct Foo { const std::optional<std::string> m_value; };
auto f1() { Foo f; return f.m_value; } // Fails: call to implicitly deleted constructor.
struct Bar { const std::string m_value; };
auto f2() { Bar b; return b.m_value; } // Works.
Is this a bug in libstdc++? Is it mixed intentions and assumptions between clang and libstdc++?
Surely the intent can't be that I can have a structure with a const std::string but I can't have a structure with a const std::optional<std::string> unless I wrote a constructor?
(In real-word cases, you'd have additional constructors, too. Thus the motivation for an = default() constructor in the first place. That, and clang-tidy.)
Edit: Here's an expanded version of the example (Compiler Explorer) showing a similar example working in "pure clang", "pure gcc", but failing in mixed "clang+libstdc++". This slightly bigger example is still artificial, but hints at why one might want to actually have such a defaulted constructor.
struct Foo
{
Foo() = default; // Implicitly deleted?!
explicit Foo(std::string arg) : m_value{std::move(arg)} {}
const auto& get() const noexcept { return m_value; }
private:
const std::optional<std::string> m_value;
};
// Maybe return an empty or a full Foo.
auto function(bool flag, std::string x)
{
Foo foo1;
Foo foo2{x};
return flag ? foo1 : foo2;
}