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
foo
asFoo foo{};
works! - Explicitly initializing the member as
const std::optional<std::string> m_value{};
works! - Removing the
const
from 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;
}
[dcl.init]/7
is likely the culprit. – Tingconst int
member (godbolt), so the problem probably has to do with= deafult
andconst
member variables. – Stunnerstd::optional
has absolutely zilch to do with it. It is because ofconst
. – Heavinessconst int value;
deletes the default constructor (sinceint
lacks a constructor). But seems likeconst optional<T> value;
should work, no matter whatT
is, especially whenconst T value;
works. – Unknownoptional
whenconst string x
works, butconst optional<string> x
doesn't work? And why does "pure clang+libc++" work and "pure gcc+libstdc++" work, but "mixed clang+libstdc++" not work? – Unknown=default
default ctor and one has{}
. I don't think the standard specifies it either way. – Heaviness