The complete (yet minimalistic still) version is available in the "Source Code", as all other examples mentioned here. Here I want to show how construction and assignment work, and how the data is accesses at runtime.

Constructors:
variant(void) {
   new (_buf) TLIST::head();
   new (_buf_wrapper) wraplist::head(); 
}

template<typename T>
variant(const T& t) {
   enum { index = index_of_type<TLIST, T>::value };

   new (_buf) T(t);
   new (_buf_wrapper) specific_type_wrapper<T>();
} 

By default, an object of the fist type in the list is constructed (thus requiring it to be default-constructible), the object is created inside _buf and the wrapper class (which has only vtable pointer) in _buf_wrapper.
The template version of constructor initializes object using copy constructor. The purpose of the first line is only to validate that type T is one of the supported types, and this is done by finding its index in the list: index_of_type<TLIST, T>::value - if not found, this line will fail to compile. Additional note regarding this constructor: it requires an object of type T exactly, so if, for example, you defined a variant of (int, double, std::string), you will fail to initialize it with const char* string, although std::string can be implicitely constructed from it. There are some techniques which can allow such initialization, but this issue is not addressed in this example.

Let's now look at full-path implementation of one of concrete methods of variant's interface, and from that you'll deduct how how all others work.

template<typename TLIST>
class variant {
   struct basic_type_wrapper {
      virtual std::ostream & print(std::ostream &out, const char *buf) const = 0;
      // ...
   };

   template<typename T>
   struct specific_type_wrapper : public basic_type_wrapper {
      inline T * getT(char *buf) { return reinterpret_cast<T*>(buf); }

      virtual std::ostream & print(std::ostream &out, const char *buf) const {
         return (out << (*getT(buf)));
      }

      // ...
   };

   char _buf[/*...*/];
   char _buf_wrapper[[/*...*/];

   inline const basic_type_wrapper * getWrapper(void) const {
      return reinterpret_cast<const basic_type_wrapper*>(_buf_wrapper);
   }

public:
   friend std::ostream & operator<<(std::ostream &out, const variant<TLIST> &v) {
      return v.getWrapper()->print(out, v._buf);
   }
}; 


Note the use of functions specific_type_wrapper::getT and getWrapper for casting of the buffers to concrete types. Although it uses reinterpret_cast, we know that his casting is safe, as we always keep the state consistent.

There are many issues not covered by this variant demo, among which:
  • Alignment of different types
  • Swapping (required by some STL algorithms)
  • Visitor implementation (construction of AbstractVisitor patterns was discussed in Typellists section)
  • Constructing variant from other types, which are convertible to one of allowed types

Last edited Dec 1, 2007 at 9:18 PM by migo, version 1

Comments

No comments yet.