In the context of boost::variant, I understand that boost::has_nothrow_copy keeps copying from allocating the object being overwritten onto the heap incase the copy throws and the original needs to be recovered (though I'm a little surprised that it doesn't do a move).
However, I'm unclear as to the purpose of boost::has_nothrow_constructor. Why does it need this? In the docs it states:
Enabling Optimizations
...
- If any bounded type is nothrow default-constructible (as indicated by
boost::has_nothrow_constructor), the library guaranteesvariantwill use only single storage and in-place construction for every bounded type in thevariant. Note, however, that in the event of assignment failure, an unspecified nothrow default-constructible bounded type will be default-constructed in the left-hand side operand so as to preserve the never-empty guarantee.
This seems to indicate that without the specialization to std::true_type, this would result in using either multi storage or non-in-place construction. What does that even mean?
NOTE: I actually found that this pdf was easier to read because it had more examples.
The answer is in your quoted piece of Boost.Variant documentation.
Boost.Variant maintains a never-empty guarantee, meaning that no matter what happens during
boost::variant's lifetime, it is guaranteed to contain exactly one object of one of the types listed in its template parameters. This poses a problem with assignment to aboost::variant- what happens if assignment fails with an exception?For example, let's consider the following piece of code:
Initially,
varcontains a value 10 of typeint. Then it is assigned a value of typestd::string, and for the sake of this example let's assume this assignment fails with an exception (e.g.std::bad_alloc).Since the original stored value was not of type
std::string, that value needs to be destroyed.inthas trivial destructor, so destroying it is a no-op. But then the storage it occupied is reused by the newly constructedstd::string, which is in this case copy-constructed fromstr. As we established above, this copy constructor throws, and there is no active value left invar- the string failed to construct and the previousintis already lost.One solution could be to use a heap-allocated storage for constructing the string first, before destroying the currently stored value in the variant. If the construction succeeds, then the stored value is destroyed, and the variant stores a pointer to the newly constructed value internally. And that is what
boost::variantdoes, with a few exceptions.First, if the type of the value that is being assigned has a non-throwing copy constructor then this whole problem is non-existent - the copy construction is guaranteed not to fail, so it is safe to destroy the original value before the copy. This is detected by
boost::has_nothrow_copy. This isn't our case in this example, asstd::string's copy constructor clearly can throw an exception.Second, if any of the types that are listed in
boost::varianttemplate parameters has a non-throwing default constructor, as determined byboost::has_nothrow_constructor, thenboost::variantcan recover from a failed assignment by default-constructing a value of one of such types. Boost.Variant doesn't specify which of the types will be chosen if multiple types have a non-throwing default constructor, except that a special typeboost::blankwill be preferred, if listed. This optimization matches our case, as the default constructor ofintis trivial and never throws. So the behavior of the assignment in our example will be as follows:int.std::stringin the internal storage ofvar.intin the internal storage and propagate the exception to the caller.As a result, the value of
varbecomes an indeterminate value of typeint(because the default constructor ofintdoes not actually initialize it).