以下代码对于 Visual Studio 编译良好< 17.2:
#include <optional>
#include <map>
class __declspec(dllexport) A {
using M = std::map<int, A>;
std::optional<M> map_;
};
但是对于 Visual Studio 17.2 及更高版本,失败,并出现错误:
example.cpp
...utility(330): error C2079: 'std::pair<const int,A>::second' uses undefined class 'A'
...optional(159): note: see reference to class template instantiation 'std::pair<const int,A>' being compiled
...optional(158): note: while compiling class template member function 'void std::_Optional_construct_base<_Ty>::_Construct_from<const _Base&>(_Self) noexcept(<expr>)'
with
[
_Ty=A::M,
_Base=std::_Optional_construct_base<A::M>,
_Self=const std::_Optional_construct_base<A::M> &
]
Compiler returned: 2
如果我从代码中删除
__declspec(dllexport)
或将std::optional
更改为boost::optional
,它在任何版本中都可以正常工作。
这是编译器错误,还是我遗漏了什么?
stl 中的“错误”不是现在出现的,而是在以前的版本中出现的。看,问题是 std 可选要求类型为
is_trivially_destructible
https://en.cppreference.com/w/cpp/types/is_destructible
从c++20版本开始,
std::optional
变成了constexpr
。因此,析构函数不是在运行时实现的,而是在编译时实现的。看
https://en.cppreference.com/w/cpp/utility/optional/~可选
回到您的代码示例,您的类
A
不是is_trivially_destructible
,因为它包含map_
的M
,它可以包含相同的类A
作为对象,而不是作为引用 - 因此,它是一个复杂的对象。
为什么 __declspec(dllexport)
会影响:在 std::optional
变成 constexpr
之前,std::optional
的析构函数已经由您的编译器在 DLL 中生成。现在它应该在编译时由将使用您的 DLL 的编译器生成,但它实际上无法生成它,因为类 A
是它的外部并且显然不是 is_trivially_destructible
。因此,编译器的行为是合理的,尽管并不明显。要更详细地了解问题,请尝试执行以下代码:
#include <optional>
#include <string>
#include <map>
class __declspec(dllexport) A {
public:
mutable std::optional<A> a_;
};
你会看到问题
is_trivially_destructible
:
error C2139: 'A': an undefined class is not allowed as an argument to compiler intrinsic type trait '__is_trivially_destructible'
我不认为
boost::optional
可以成为解决方案,因为也许在
constexpr
的下一个版本中它也会出现。正确的改变是简化代码逻辑并通过指针或引用存储类,在这种情况下,std::optional
的类型将变得微不足道。