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.
Foo() = default;
constructor gives the same results.Foo() {}
works!foo
as Foo foo{};
works!const std::optional<std::string> m_value{};
works!const
from the member works! (but isn't the same meaning)-stdlib=libc++
works!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;
}
If no user-defined constructors are present and the implicitly-declared default constructor is not trivial, the user may still inhibit the automatic generation of an implicitly-defined default constructor by the compiler with the keyword delete.
The copy constructor and copy-assignment operator are public but deleted. It is a compile-time error to define or call a deleted function. The intent is clear to anyone who understands =default and =delete . You don't have to understand the rules for automatic generation of special member functions.
This is a combination of:
Firstly, the Standard does not specify whether for the default optional.ctor a permissible implementation would be to defined it as defaulted:
constexpr optional() noexcept = default;
^^^^^^^^^ OK?
Note that functions.within.classes answers the question in the affirmative for copy/move constructors, assignment operators, and non-virtual destructors, but does not mention default constructors.
This matters because it affects program correctness; on the assumption that optional
has data members approximating the following:
template<class T>
class optional {
alignas(T) byte buf[sizeof(T)]; // no NSDMI
bool engaged = false;
// ...
};
then since buf
is a direct non-variant non-static data member lacking a default member initializer, if the default constructor of optional
is defined as defaulted and thus is not user-provided, optional
is not const-default-constructible and so optional<A> const a;
is ill-formed.
It is thus a bad idea for a Library to define the default constructor of optional
as defaulted, not only for this reason but also because it makes a value-initialized optional<B> b{};
perform more work than necessary, since it must zero-initialize buf
, as observed std::optional - construct empty with {} or std::nullopt? - see in particular this answer. libstdc++ is fixed in this commit, which will be included in the next release of gcc, presumptively gcc 11.
Finally, it is a bug in gcc that it allows a non-const-default-constructible type for a const
non-static data member without default member initializer of a class type whose default constructor is defined as defaulted; clang is correct to reject it. A reduced testcase is:
struct S {
S() = default;
int const i;
};
The best workaround in your case would be to supply a NSDMI:
const std::optional<std::string> m_value = std::nullopt;
^^^^^^^^^^^^^^
or (though I prefer the former, as it gives better codegen under Clang/libstdc++):
const std::optional<std::string> m_value = {};
^^^^
You might also consider giving Foo
a user-defined default constructor; this results in better codegen under gcc (not zeroing the buffer but only setting the engaged
member to false
) thanks to what is presumably a related compiler bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With