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.

timrau
  • 22,578
  • 4
  • 51
  • 64
salvador p
  • 2,905
  • 5
  • 26
  • 26

3 Answers3

82

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.

sholsapp
  • 15,542
  • 10
  • 50
  • 67
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • 6
    By the way, this is simply type-erasure. A very useful principle. – Matthieu M. Feb 14 '11 at 07:51
  • 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? – Alex Botev Dec 25 '16 at 19:47
  • @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. – niraami Jan 22 '17 at 23:51
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.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • 4
    You can also see the source on their website. For example, [here's the contents for any.hpp](http://www.boost.org/doc/libs/release/boost/any.hpp) and [here's the contents of variant.hpp](http://www.boost.org/doc/libs/release/boost/variant.hpp). The `#include`s are hyperlinked so you can directly go see the files they `#include`. – In silico Feb 14 '11 at 05:05
  • 15
    This answer is just plain wrong. Any does NOT, I repeat does NOT use void pointers. – Edward Strange Feb 14 '11 at 05:36
  • 7
    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. – templatetypedef Feb 14 '11 at 05:49
  • 2
    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 as subclasses of any::placeholder. – Michael Aaron Safyan Feb 15 '11 at 08:32
  • +1 for the explanation on the `variant` type, since all other answers don't cover it. – legends2k Apr 03 '14 at 15:24
  • Then why do I get an error: `conversion from 'std::vector' to non-scalar type 'std::vector' requested`? – IgorGanapolsky Jun 08 '16 at 14:27
  • 1
    @IgorGanapolsky 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). – Michael Aaron Safyan Jun 08 '16 at 15:55
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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252