The following code compiles well for Visual Studio < 17.2:
#include <optional>
#include <map>
class __declspec(dllexport) A {
using M = std::map<int, A>;
std::optional<M> map_;
};
But failed for Visual Studio 17.2 and newer with an error:
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
It works fine in any version if I drop __declspec(dllexport)
from the code or change std::optional
to boost::optional
.
Is this compiler bug, or I’m missing something?
Behavior is the same for both C++17 and C++20 mode.
1
This is not a compiler error, but a change in STL with the advent of c++20. Look, the problem is that std::optional
requires the type to be is_trivially_destructible
https://en.cppreference.com/w/cpp/types/is_destructible
Since version c++20, std::optional
became constexpr
. Therefore, the destructor is implemented not at runtime, but at compile time. See
https://en.cppreference.com/w/cpp/utility/optional/~optional
Returning to your code example, your class A
is not is_trivially_destructible
because it contains a map_
of M
, which can contain the same class A
as an object, not as a reference – thus, it is a complex object.
Why does __declspec(dllexport)
affect: before std::optional
became constexpr
, the destructor for std::optional
was already generated by your compiler in your DLL. Now it should be generated at compile time by the compiler that will use your DLL, but it actually cannot generate it, since class A
is external to it and is clearly not is_trivially_destructible
.
Therefore, the compiler’s behavior is justified, although not obvious. To understand the problem in more detail, try to execute the following code:
#include <optional>
#include <string>
#include <map>
class __declspec(dllexport) A {
public:
mutable std::optional<A> a_;
};
and you will see the problem with is_trivially_destructible
:
error C2139: 'A': an undefined class is not allowed as an argument to compiler intrinsic type trait '__is_trivially_destructible'
I do not think that boost::optional
can be a solution, since, perhaps, in the next versions of constexpr
it will come to it too. The correct change is to simplify the logic of the code and store the class by pointer or reference, in which case the type for std::optional
will be trivial.
6