问题描述
使用std::optional
来考虑以下结构,其中包含肯定具有“正常”默认构造函数的类型。
#include <optional>
#include <string>
struct Foo
{
Foo() = default;
const std::optional<std::string> m_value;
};
bool function()
{
Foo foo;
return bool(foo.m_value);
}
使用clang 9编译以下内容(使用其gcc 8使用系统默认的libstdc++
)会产生意外警告:
<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;
^
Foo foo;
也存在严重错误,因为它使用了已删除的构造函数。
- 删除
Foo() = default;
构造函数会得到相同的结果。 - 用
Foo() {}
代替它是可行的! - 删除所有构造函数并将
foo
初始化为Foo foo{};
即可! - 将成员明确初始化为
const std::optional<std::string> m_value{};
即可! - 从成员中删除
const
是可行的! (但含义不同) - 将clang 9与
-stdlib=libc++
一起使用会有效! - 使用gcc 8.3(仍与
libstdc++
一起使用)可以实现!
我已经读过std::optional - construct empty with {} or std::nullopt?,这似乎表明libstdc++
的{{1}}构造函数的= default
实现选择很可能是原因。但是,在这个问题上,关注点是一种方法相对于另一种方法的效率问题。在这种情况下,这似乎是正确性的问题。
(我怀疑对How can std::chrono::duration::duration() be constexpr?的回答将成为这里故事的一部分。)
我在Compiler Explorer上看到了相同的行为:https://gcc.godbolt.org/z/Yj1o5P
比较可选和非可选std::optional
的简单结构(在非工作配置中):
std::string
这是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.
中的错误吗?是在clang和libstdc++
之间混合了意图和假设吗?
当然不是我可以拥有一个libstdc++
的结构,但是除非我写了一个构造函数,否则我不能拥有一个const std::string
的结构?
(在实词情况下,您还会有其他构造函数。因此,首先使用const std::optional<std::string>
构造函数的动机是这样。而且很整洁。)
编辑:这是示例(Compiler Explorer)的扩展版本,显示了在“纯clang”,“纯gcc”中工作的相似示例,但在混合的“ clang + libstdc ++”中失败。这个稍微大一点的示例仍然是人为的,但它暗示了为什么人们可能想要真正拥有这样的默认构造函数。
= default()
解决方法
这是以下各项的组合:
- 标准中的规格不足;
- 次佳的Library实现;和
- 编译器错误。
首先,该标准未指定默认的optional.ctor是否允许将其定义为默认实现:
constexpr optional() noexcept = default;
^^^^^^^^^ OK?
请注意,functions.within.classes肯定地回答了复制/移动构造函数,赋值运算符和非虚拟析构函数的问题,但未提及默认构造函数。
这很重要,因为它会影响程序的正确性;假设optional
的数据成员近似如下:
template<class T>
class optional {
alignas(T) byte buf[sizeof(T)]; // no NSDMI
bool engaged = false;
// ...
};
然后,由于buf
是缺少默认成员初始值设定项的直接非变量非静态数据成员,因此,如果optional
的默认构造函数被定义为默认构造函数,因此不是用户提供的,{ {1}}不是const-default-constructible,所以optional
格式错误。
因此,对于图书馆而言,将默认的optional<A> const a;
定义为默认构造函数是一个坏主意,不仅是因为这个原因,而且还因为它会使值初始化的optional
执行比必要的更多工作,因为必须将optional<B> b{};
初始化为std::optional - construct empty with {} or std::nullopt?,因此this answer-尤其请参见this commit。 libstdc ++已在reduced testcase中进行了修复,该版本将包含在gcc的下一发行版中,可能是gcc 11。
最后,这是gcc中的一个错误,它允许buf
非静态数据成员使用非常量默认可构造类型,而没有默认构造函数定义为默认类型的类类型的默认成员初始化程序; clang拒绝它是正确的。 gives better codegen是:
const
针对您的情况,最好的解决方法是提供NSDMI:
struct S {
S() = default;
int const i;
};
或(尽管我更喜欢前者,因为它在Clang / libstdc ++下是results in better codegen):
const std::optional<std::string> m_value = std::nullopt;
^^^^^^^^^^^^^^
您还可以考虑为const std::optional<std::string> m_value = {};
^^^^
提供一个用户定义的默认构造函数;由于可能是相关的编译器错误,此gcc下的the fine manual(不会将缓冲区清零,而仅将Foo
成员设置为engaged
)。