blob: f6282d4dcfa5357ccac5d8daaf7e635668de6933 [file] [log] [blame]
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN"
"http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
<section id="variant.design">
<title>Design Overview</title>
<using-namespace name="boost"/>
<section id="variant.design.never-empty">
<title>&quot;Never-Empty&quot; Guarantee</title>
<section id="variant.design.never-empty.guarantee">
<title>The Guarantee</title>
<para>All instances <code>v</code> of type
<code><classname>variant</classname>&lt;T1,T2,...,TN&gt;</code>
guarantee that <code>v</code> has constructed content of one of the
types <code>T<emphasis>i</emphasis></code>, even if an operation on
<code>v</code> has previously failed.</para>
<para>This implies that <code>variant</code> may be viewed precisely as
a union of <emphasis>exactly</emphasis> its bounded types. This
&quot;never-empty&quot; property insulates the user from the
possibility of undefined <code>variant</code> content and the
significant additional complexity-of-use attendant with such a
possibility.</para>
</section>
<section id="variant.design.never-empty.problem">
<title>The Implementation Problem</title>
<para>While the
<link linkend="variant.design.never-empty.guarantee">never-empty guarantee</link>
might at first seem &quot;obvious,&quot; it is in fact not even
straightforward how to implement it in general (i.e., without
unreasonably restrictive additional requirements on
<link linkend="variant.concepts.bounded-type">bounded types</link>).</para>
<para>The central difficulty emerges in the details of
<code>variant</code> assignment. Given two instances <code>v1</code>
and <code>v2</code> of some concrete <code>variant</code> type, there
are two distinct, fundamental cases we must consider for the assignment
<code>v1 = v2</code>.</para>
<para>First consider the case that <code>v1</code> and <code>v2</code>
each contains a value of the same type. Call this type <code>T</code>.
In this situation, assignment is perfectly straightforward: use
<code>T::operator=</code>.</para>
<para>However, we must also consider the case that <code>v1</code> and
<code>v2</code> contain values <emphasis>of distinct types</emphasis>.
Call these types <code>T</code> and <code>U</code>. At this point,
since <code>variant</code> manages its content on the stack, the
left-hand side of the assignment (i.e., <code>v1</code>) must destroy
its content so as to permit in-place copy-construction of the content
of the right-hand side (i.e., <code>v2</code>). In the end, whereas
<code>v1</code> began with content of type <code>T</code>, it ends
with content of type <code>U</code>, namely a copy of the content of
<code>v2</code>.</para>
<para>The crux of the problem, then, is this: in the event that
copy-construction of the content of <code>v2</code> fails, how can
<code>v1</code> maintain its &quot;never-empty&quot; guarantee?
By the time copy-construction from <code>v2</code> is attempted,
<code>v1</code> has already destroyed its content!</para>
</section>
<section id="variant.design.never-empty.memcpy-solution">
<title>The &quot;Ideal&quot; Solution: False Hopes</title>
<para>Upon learning of this dilemma, clever individuals may propose the
following scheme hoping to solve the problem:
<orderedlist>
<listitem>Provide some &quot;backup&quot; storage, appropriately
aligned, capable of holding values of the contained type of the
left-hand side.</listitem>
<listitem>Copy the memory (e.g., using <code>memcpy</code>) of the
storage of the left-hand side to the backup storage.</listitem>
<listitem>Attempt a copy of the right-hand side content to the
(now-replicated) left-hand side storage.</listitem>
<listitem>In the event of an exception from the copy, restore the
backup (i.e., copy the memory from the backup storage back into
the left-hand side storage).</listitem>
<listitem>Otherwise, in the event of success, now copy the memory
of the left-hand side storage to another &quot;temporary&quot;
aligned storage.</listitem>
<listitem>Now restore the backup (i.e., again copying the memory)
to the left-hand side storage; with the &quot;old&quot; content
now restored, invoke the destructor of the contained type on the
storage of the left-hand side.</listitem>
<listitem>Finally, copy the memory of the temporary storage to the
(now-empty) storage of the left-hand side.</listitem>
</orderedlist>
</para>
<para>While complicated, it appears such a scheme could provide the
desired safety in a relatively efficient manner. In fact, several
early iterations of the library implemented this very approach.</para>
<para>Unfortunately, as Dave Abraham's first noted, the scheme results
in undefined behavior:
<blockquote>
<para>&quot;That's a lot of code to read through, but if it's
doing what I think it's doing, it's undefined behavior.</para>
<para>&quot;Is the trick to move the bits for an existing object
into a buffer so we can tentatively construct a new object in
that memory, and later move the old bits back temporarily to
destroy the old object?</para>
<para>&quot;The standard does not give license to do that: only one
object may have a given address at a time. See 3.8, and
particularly paragraph 4.&quot;</para>
</blockquote>
</para>
<para>Additionally, as close examination quickly reveals, the scheme has
the potential to create irreconcilable race-conditions in concurrent
environments.</para>
<para>Ultimately, even if the above scheme could be made to work on
certain platforms with particular compilers, it is still necessary to
find a portable solution.</para>
</section>
<section id="variant.design.never-empty.double-storage-solution">
<title>An Initial Solution: Double Storage</title>
<para>Upon learning of the infeasibility of the above scheme, Anthony
Williams proposed in
<link linkend="variant.refs.wil02">[Wil02]</link> a scheme that served
as the basis for a portable solution in some pre-release
implementations of <code>variant</code>.</para>
<para>The essential idea to this scheme, which shall be referred to as
the &quot;double storage&quot; scheme, is to provide enough space
within a <code>variant</code> to hold two separate values of any of
the bounded types.</para>
<para>With the secondary storage, a copy the right-hand side can be
attempted without first destroying the content of the left-hand side;
accordingly, the content of the left-hand side remains available in
the event of an exception.</para>
<para>Thus, with this scheme, the <code>variant</code> implementation
needs only to keep track of which storage contains the content -- and
dispatch any visitation requests, queries, etc. accordingly.</para>
<para>The most obvious flaw to this approach is the space overhead
incurred. Though some optimizations could be applied in special cases
to eliminate the need for double storage -- for certain bounded types
or in some cases entirely (see
<xref linkend="variant.design.never-empty.optimizations"/> for more
details) -- many users on the Boost mailing list strongly objected to
the use of double storage. In particular, it was noted that the
overhead of double storage would be at play at all times -- even if
assignment to <code>variant</code> never occurred. For this reason
and others, a new approach was developed.</para>
</section>
<section id="variant.design.never-empty.heap-backup-solution">
<title>Current Approach: Temporary Heap Backup</title>
<para>Despite the many objections to the double storage solution, it was
realized that no replacement would be without drawbacks. Thus, a
compromise was desired.</para>
<para>To this end, Dave Abrahams suggested to include the following in
the behavior specification for <code>variant</code> assignment:
&quot;<code>variant</code> assignment from one type to another may
incur dynamic allocation." That is, while <code>variant</code> would
continue to store its content <emphasis>in situ</emphasis> after
construction and after assignment involving identical contained types,
<code>variant</code> would store its content on the heap after
assignment involving distinct contained types.</para>
<para>The algorithm for assignment would proceed as follows:
<orderedlist>
<listitem>Copy-construct the content of the right-hand side to the
heap; call the pointer to this data <code>p</code>.</listitem>
<listitem>Destroy the content of the left-hand side.</listitem>
<listitem>Copy <code>p</code> to the left-hand side
storage.</listitem>
</orderedlist>
Since all operations on pointers are nothrow, this scheme would allow
<code>variant</code> to meet its never-empty guarantee.
</para>
<para>The most obvious concern with this approach is that while it
certainly eliminates the space overhead of double storage, it
introduces the overhead of dynamic-allocation to <code>variant</code>
assignment -- not just in terms of the initial allocation but also
as a result of the continued storage of the content on the heap. While
the former problem is unavoidable, the latter problem may be avoided
with the following &quot;temporary heap backup&quot; technique:
<orderedlist>
<listitem>Copy-construct the content of the
<emphasis>left</emphasis>-hand side to the heap; call the pointer to
this data <code>backup</code>.</listitem>
<listitem>Destroy the content of the left-hand side.</listitem>
<listitem>Copy-construct the content of the right-hand side in the
(now-empty) storage of the left-hand side.</listitem>
<listitem>In the event of failure, copy <code>backup</code> to the
left-hand side storage.</listitem>
<listitem>In the event of success, deallocate the data pointed to
by <code>backup</code>.</listitem>
</orderedlist>
</para>
<para>With this technique: 1) only a single storage is used;
2) allocation is on the heap in the long-term only if the assignment
fails; and 3) after any <emphasis>successful</emphasis> assignment,
storage within the <code>variant</code> is guaranteed. For the
purposes of the initial release of the library, these characteristics
were deemed a satisfactory compromise solution.</para>
<para>There remain notable shortcomings, however. In particular, there
may be some users for which heap allocation must be avoided at all
costs; for other users, any allocation may need to occur via a
user-supplied allocator. These issues will be addressed in the future
(see <xref linkend="variant.design.never-empty.roadmap"/>). For now,
though, the library treats storage of its content as an implementation
detail. Nonetheless, as described in the next section, there
<emphasis>are</emphasis> certain things the user can do to ensure the
greatest efficiency for <code>variant</code> instances (see
<xref linkend="variant.design.never-empty.optimizations"/> for
details).</para>
</section>
<section id="variant.design.never-empty.optimizations">
<title>Enabling Optimizations</title>
<para>As described in
<xref linkend="variant.design.never-empty.problem"/>, the central
difficulty in implementing the never-empty guarantee is the
possibility of failed copy-construction during <code>variant</code>
assignment. Yet types with nothrow copy constructors clearly never
face this possibility. Similarly, if one of the bounded types of the
<code>variant</code> is nothrow default-constructible, then such a
type could be used as a safe &quot;fallback&quot; type in the event of
failed copy construction.</para>
<para>Accordingly, <code>variant</code> is designed to enable the
following optimizations once the following criteria on its bounded
types are met:
<itemizedlist>
<listitem>For each bounded type <code>T</code> that is nothrow
copy-constructible (as indicated by
<code><classname>boost::has_nothrow_copy</classname></code>), the
library guarantees <code>variant</code> will use only single
storage and in-place construction for <code>T</code>.</listitem>
<listitem>If <emphasis>any</emphasis> bounded type is nothrow
default-constructible (as indicated by
<code><classname>boost::has_nothrow_constructor</classname></code>),
the library guarantees <code>variant</code> will use only single
storage and in-place construction for <emphasis>every</emphasis>
bounded type in the <code>variant</code>. 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.</listitem>
</itemizedlist>
</para>
<para><emphasis role="bold">Caveat</emphasis>: On most platforms, the
<libraryname>Type Traits</libraryname> templates
<code>has_nothrow_copy</code> and <code>has_nothrow_constructor</code>
by default return <code>false</code> for all <code>class</code> and
<code>struct</code> types. It is necessary therefore to provide
specializations of these templates as appropriate for user-defined
types, as demonstrated in the following:
<programlisting>// ...in your code (at file scope)...
namespace boost {
template &lt;&gt;
struct <classname>has_nothrow_copy</classname>&lt; myUDT &gt;
: <classname>mpl::true_</classname>
{
};
}
</programlisting>
</para>
<para><emphasis role="bold">Implementation Note</emphasis>: So as to make
the behavior of <code>variant</code> more predictable in the aftermath
of an exception, the current implementation prefers to default-construct
<code><classname>boost::blank</classname></code> if specified as a
bounded type instead of other nothrow default-constructible bounded
types. (If this is deemed to be a useful feature, it will become part
of the specification for <code>variant</code>; otherwise, it may be
obsoleted. Please provide feedback to the Boost mailing list.)</para>
</section>
<section id="variant.design.never-empty.roadmap">
<title>Future Direction: Policy-based Implementation</title>
<para>As the previous sections have demonstrated, much effort has been
expended in an attempt to provide a balance between performance, data
size, and heap usage. Further, significant optimizations may be
enabled in <code>variant</code> on the basis of certain traits of its
bounded types.</para>
<para>However, there will be some users for whom the chosen compromise
is unsatisfactory (e.g.: heap allocation must be avoided at all costs;
if heap allocation is used, custom allocators must be used; etc.). For
this reason, a future version of the library will support a
policy-based implementation of <code>variant</code>. While this will
not eliminate the problems described in the previous sections, it will
allow the decisions regarding tradeoffs to be decided by the user
rather than the library designers.</para>
</section>
</section>
</section>