Skip to content

Implementing the standard variant

December 22, 2015

The latest proposal for a standard variant is P0088R0, found at http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0088r0.pdf

I have now implemented a superset of the proposal, which can be found at the git repository, https://github.com/jarro2783/thenewcpp/. The two extra features are support for recursive types, and the visitor takes arbitrary extra arguments.

The variant requires C++14, which has made the implementation significantly easier compared to C++11. Return type deduction for functions has meant that I can drop result_type for visitors and I don’t need to rely on complex or redundant uses of decltype.

The current proposal allows for the variant to be empty, but it will rarely happen, and it is up to the user to check before visiting an empty variant, leaving the results undefined otherwise. The only situation in which a variant object could be empty is if move/copy assignment between different types throws, and presumably if the user doesn’t abort their program at this point they will have caught the exception and know to check the affected variant.

I think that this proposal is an improvement on the last one in regards to empty visitation. The dual visit functions were a tad clunky, and the no-parameter operator() meant that everything had to account for the rare case of an empty variant. Furthermore the two-argument version of the visit function meant that it would have likely been impossible to implement visitation with extra arbitrary arguments.

Implementation

Most of the implementation was straight-forward, and extending my variant to meet the standard mostly meant being pedantic about every version of the constructor, emplace and get, with regards to pointers, const and rvalue references.

The return type of the visitor posed a minor problem, and I had to resort to a bit of a hack to get it to work. If anyone can come up with any better ideas I would love to hear them. In most places I could use decltype(auto) for the return type. The only problem was the static array of visit functions that dispatch the visit call based on the current index of the variant.

static whichCaller callers[sizeof...(AllTypes)] =
          {
            &visitor_caller<Internal&&, AllTypes,
              VoidPtrCV&&, Visitor, Args&&...>...
          }
        ;

The problem here is that you can’t have an array of auto, so you actually need to know the type of the visitor functions. The hack then is that I declared the type whichCaller to be decltype of an expression calling visitor_caller with those exact arguments above, except that AllTypes is replaced with the first type in the variant. Every operator() of the variant needs to return the same thing, so this is ok, but it still seems a bit hacky.

typedef
decltype (visitor_caller<Internal&&, First, VoidPtrCV&&, Visitor,
  Args&&...>
  (
    std::forward<Internal>(internal),
    std::forward<VoidPtrCV>(storage),
    std::forward<Visitor>(visitor),
    std::forward<Args>(args)...
  ))
result;

typedef result
  (*whichCaller)(Internal&&, VoidPtrCV&&, Visitor&&, Args&&...);

Multi visitation

The other interesting part of the implementation is the multi visitor, which allows multiple variants to be visited. I added arbitrary arguments in addition to plain multi-visitation. To add that I matched variants in the multi visitors operator(), and once there were no more variants I passed all the visited values on to the user’s visitor.

The proposal

I think that the proposal is quite good. The use of get<I> to access the variant makes accesses faster, and removes the get visitor that I previously used.

There might be problems with assignment if the right-hand side is in a subtree of the left-hand side. This is a problem with or without the recursive wrapper, since you could still make your own recursive variant using unique_ptr. Essentially the right-hand side would be destroyed by destroying the left-hand side, then the right-hand side would be a dangling reference. However, the alternative of moving the lhs into a temporary has problems because it changes the expected order of object destruction and copying, and move construction might be disabled for an object. I suspect it is better to leave things as they are and the user must know that assigning from a subtree is impossible.

The proposal has a few rough edges, and there are quite a few typographical mistakes to fix up. For example, the definitions refer to get_if for the pointer versions where the synopsis at the top refers to get. However, I’m sure these things will all be fixed up in good time, and they certainly aren’t show stoppers.

I don’t understand the reasoning behind the restrictions in multiple places that T occur once in Types.... To me it seems like most of those cases would be ok since the first occurrence would be picked. If the user wants to do variant v (emplaced_index_t<1>, 42) and then do get>int<(v), then it won’t work and that’s their own silly fault. But it will only throw, it won’t blow up.

I found the wording of operator=(T&&) to be thoroughly confusing and unclear, which is problematic since it is probably the most important function in the whole proposal. I worked it out in the end, but it could be improved significantly.

I also didn’t understand why it was necessary to have two versions of the emplace functions, one with Args&&..., and the other with initializer_list<U>, Args&&.... I would have thought that the first subsumes the second.

Otherwise, I found no major problems with the proposal in implementing it, just a few rough edges where I had to work out the right way to do things since the proposal was clearly in error (missing return types, wrong return types, definition inconsistent with synopsis etc.)

Advertisements
7 Comments
  1. Kristine permalink

    There seems to be a problem with the code examples using HTML escapes.

  2. > edges where I had to work out the right way to do things since the proposal was clearly in error

    Please send a list of those rough edges to the proposal author (and you can cc me as well). They’ll need to be fixed in the next version of the proposal.

  3. Your implementation doesn’t seem to like Clang 3.7 very much :(

    • I’m on holidays so I can’t look at this yet. Is it compiled with c++14? It works with gcc 5.3, so there could be a bug in clang.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: