How do boost::variant and boost::any work?
Asked Answered
L

3

68

How do variant and any from the boost library work internally? In a project I am working on, I currently use a tagged union. I want to use something else, because unions in C++ don't let you use objects with constructors, destructors or overloaded assignment operators.

I queried the size of any and variant, and did some experiments with them. In my platform, variant takes the size of its longest possible type plus 8 bytes: I think it my just be 8 bytes o type information and the rest being the stored value. On the other hand, any just takes 8 bytes. Since i'm on a 64-bit platform, I guess any just holds a pointer.

How does Any know what type it holds? How does Variant achieve what it does through templates? I would like to know more about these classes before using them.

Lavernalaverne answered 14/2, 2011 at 4:53 Comment(0)
I
83

If you read the boost::any documentation they provide the source for the idea: http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf

It's basic information hiding, an essential C++ skill to have. Learn it!

Since the highest voted answer here is totally incorrect, and I have my doubts that people will actually go look at the source to verify that fact, here's a basic implementation of an any like interface that will wrap any type with an f() function and allow it to be called:

struct f_any
{
   f_any() : ptr() {}
   ~f_any() { delete ptr; }
   bool valid() const { return ptr != 0; }
   void f() { assert(ptr); ptr->f(); }

   struct placeholder
   {
     virtual ~placeholder() {}
     virtual void f() const = 0;
   };

   template < typename T >
   struct impl : placeholder
   {
     impl(T const& t) : val(t) {}
     void f() const { val.f(); }
     T val;
    };
   // ptr can now point to the entire family of 
   // struct types generated from impl<T>
   placeholder * ptr;

   template < typename T >
   f_any(T const& t) : ptr(new impl<T>(t)) {}

  // assignment, etc...
};

boost::any does the same basic thing except that f() actually returns typeinfo const& and provides other information access to the any_cast function to work.

Implausible answered 14/2, 2011 at 5:35 Comment(3)
By the way, this is simply type-erasure. A very useful principle.Preparatory
I actually wondered recently, why it is not a unique_ptr rather than a raw pointer? Is there any good reason for this or it is somewhat performance related? This can potentially avoid the delete, no?Aggregate
@Belov I don't really see a problem in using unique_ptr (Although I'm not an expert in smart pointers). So I think it's because this answer was originally posted before the C++11 standard was approved, and no-one really bothered to fix such a minor thing since then.Schopenhauer
F
19

The key difference between boost::any and boost::variant is that any can store any type, while variant can store only one of a set of enumerated types. The any type stores a void* pointer to the object, as well as a typeinfo object to remember the underlying type and enforce some degree of type safety. In boost::variant, it computes the maximum sized object, and uses "placement new" to allocate the object within this buffer. It also stores the type or the type index.

Note that if you have Boost installed, you should be able to see the source files in "any.hpp" and "variant.hpp". Just search for "include/boost/variant.hpp" and "include/boost/any.hpp" in "/usr", "/usr/local", and "/opt/local" until you find the installed headers, and you can take a look.

Edit
As has been pointed out in the comments below, there was a slight inaccuracy in my description of boost::any. While it can be implemented using void* (and a templated destroy callback to properly delete the pointer), the actualy implementation uses any<T>::placeholder*, with any<T>::holder<T> as subclasses of any<T>::placeholder for unifying the type.

Flyspeck answered 14/2, 2011 at 5:0 Comment(7)
You can also see the source on their website. For example, here's the contents for any.hpp and here's the contents of variant.hpp. The #includes are hyperlinked so you can directly go see the files they #include.Materiel
This answer is just plain wrong. Any does NOT, I repeat does NOT use void pointers.Implausible
Crazy Eddie is correct; any is not implemented using void * because it makes resource cleanup harder. Rather, it uses a polymorphic class hierarchy with a derived class template for each type stored. This allows cleanup via a virtual destructor. However, the rest of your answer is really great, so I didn't add my own downvote.Nymph
Sorry, I stand corrected. I've seen similar implementations that use a void* pointer with a templated destroy function, but you're right, boost is using any::placeholder* instead of void*, with any::holder<T> as subclasses of any::placeholder.Flyspeck
+1 for the explanation on the variant type, since all other answers don't cover it.Glomeration
Then why do I get an error: conversion from 'std::vector<boost::any>' to non-scalar type 'std::vector<int>' requested?Cockiness
@Cockiness implicit/automatic conversion only goes so far. To convert those two, you will need to copy/convert the elements. (This, by the way, is a good thing, as it prevents you from doing a lot of expensive copies unwittingly).Flyspeck
S
8

boost::any just snapshots the typeinfo while the templated constructor runs: it has a pointer to a non-templated base class that provides access to the typeinfo, and the constructor derived a type-specific class satisfying that interface. The same technique can actually be used to capture other common capabilities of a set of types (e.g. streaming, common operators, specific functions), though boost doesn't offer control of this.

boost::variant is conceptually similar to what you've done before, but by not literally using a union and instead taking a manual approach to placement construction/destruction of objects in its buffer (while handling alignment issues explicitly) it works around the restrictions that C++ has re complex types in actual unions.

Stettin answered 14/2, 2011 at 5:13 Comment(1)
Since this answer was written (possibly), boost typeerasure lets you capture other properties of types.Fishery

© 2022 - 2024 — McMap. All rights reserved.