Bra*_*cer 14 c++ libstdc++ language-lawyer clang++ c++17
考虑以下结构,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);
}
Run Code Online (Sandbox Code Playgroud)
使用 clang 9 编译以下内容(使用系统的默认值libstdc++,对于它的 gcc 8)会给出一个意外警告:
<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;
^
Run Code Online (Sandbox Code Playgroud)
Foo foo;由于它使用了所述已删除的构造函数,因此也存在硬错误。
Foo() = default;构造函数会产生相同的结果。Foo() {}作品代替!foo为Foo foo{};工作!const std::optional<std::string> m_value{};作品!const从成员中删除!(但意思不一样)-stdlib=libc++作品中使用 clang 9 !libstdc++)有效!我读过std::optional - 用 {} 或 std::nullopt 构造空?这似乎表明构造函数的libstdc++实现选择可能是罪魁祸首。但是,在这个问题上,关注的是一种方法与另一种方法的效率问题。在这种情况下,这似乎是一个正确性问题。= defaultstd::optional
(我怀疑How can std::chrono::duration::duration() be constexpr?的答案将成为这里故事的一部分。)
我在编译器资源管理器上看到相同的行为:https : //gcc.godbolt.org/z/Yj1o5P
比较可选和非可选的简单结构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.
Run Code Online (Sandbox Code Playgroud)
这是一个错误libstdc++吗?在 clang 和 之间是混合的意图和假设libstdc++吗?
当然,意图不可能是我可以拥有一个带有 a 的结构,const std::string但const std::optional<std::string>除非我编写了一个构造函数,否则我不能拥有一个带有 a的结构?
(在实际情况下,你也会有额外的构造函数。因此= default()首先构造构造函数的动机。那,和叮当声。)
编辑:这是示例(编译器资源管理器)的扩展版本,显示了一个类似的示例在“pure clang”、“pure gcc”中工作,但在混合“clang+libstdc++”中失败。这个稍微大一点的例子仍然是人为的,但暗示了为什么人们可能想要真正拥有这样一个默认的构造函数。
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;
}
Run Code Online (Sandbox Code Playgroud)
这是以下内容的组合:
首先,标准没有指定默认的option.ctor是否允许将其定义为默认的实现:
constexpr optional() noexcept = default;
^^^^^^^^^ OK?
Run Code Online (Sandbox Code Playgroud)
请注意,functions.within.classes对复制/移动构造函数、赋值运算符和非虚拟析构函数的问题回答是肯定的,但没有提及默认构造函数。
这很重要,因为它会影响程序的正确性;假设optional数据成员近似如下:
template<class T>
class optional {
alignas(T) byte buf[sizeof(T)]; // no NSDMI
bool engaged = false;
// ...
};
Run Code Online (Sandbox Code Playgroud)
那么,sincebuf是一个缺乏默认成员初始值设定项的直接非变体非静态数据成员,如果 的默认构造函数optional被定义为默认的,因此不是用户提供的,optional则不是const-default-constructible,因此optional<A> const a;格式错误。
因此,对于库来说,将默认构造函数定义optional为 defaulted 是一个坏主意,不仅是因为这个原因,还因为它使值初始化optional<B> b{};执行了不必要的工作,因为它必须为零初始化buf,正如观察到的std::可选 - 使用 {} 或 std::nullopt 构造空?- 特别参见这个答案。libstdc++ 已在此提交中修复,它将包含在 gcc 的下一个版本(假定为 gcc 11)中。
最后,gcc 中的一个错误是,它允许const非静态数据成员使用非 const-default-constructible 类型,而没有默认构造函数定义为 defaulted 的类类型的默认成员初始值设定项;clang 拒绝它是正确的。减少的测试用例是:
struct S {
S() = default;
int const i;
};
Run Code Online (Sandbox Code Playgroud)
针对您的情况,最好的解决方法是提供 NSDMI:
const std::optional<std::string> m_value = std::nullopt;
^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
或者(虽然我更喜欢前者,因为它在 Clang/libstdc++ 下提供了更好的代码生成):
const std::optional<std::string> m_value = {};
^^^^
Run Code Online (Sandbox Code Playgroud)
您还可以考虑提供Foo用户定义的默认构造函数;由于可能是相关的编译器错误,这会在 gcc 下产生更好的代码生成(不将缓冲区清零,而仅将engaged成员设置为)。false
| 归档时间: |
|
| 查看次数: |
266 次 |
| 最近记录: |