Because one of my goals is to rely on as few external libraries as possible, I am not using Boost. As a result, there comes times when I need to roll my own, extremely useful, library-like code that isn’t a part of the C++ standard yet.
One of these, is the Variant. A tagged union / discriminated union / sum type, that allows you to store multiple data types in one section of memory. A sort of type-erasure gimmick.
I need a type like his for two reasons so far. The first is that my scripting language, space script, is quasi-type safe. You define variables using your usual data types, but any variable type can be cast to any other type.
I did it this way mostly because I was curious to see how a language like that would behave. Probably not the best idea.
I’ve also allowed my Variant
type to be recursive. This means that it can contain members of itself. I’ve done this specifically because my data format, space format, is JSON-esque, and basically every node can contain any number of numbers, strings and other objects etc. That’s the second reason I need a variant type.
This took me quite a bit of screwing around to get right. A lot of the code for the base Variant was taken from someone else’s code that I found online. This code was fundamentally broken though; so I’ve fixed it up and allowed for recursion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 |
#ifndef VARIANT_HPP_INCLUDED #define VARIANT_HPP_INCLUDED #include <typeindex> #include <type_traits> #include <memory> #include "CPPutils.hpp" namespace utils { /** \defgroup Substitution * Used in order to substitute one type for another * \{ * */ /** \brief Standard tag type * * \tparam T the type * */ template<class T> struct Tag { using type=T; /**< The type of this Tag */ }; /** \brief Substitute is used to derive the correct Tag type * * \tparam X the type that will be used by default * \tparam A the type to check X against * \tparam B the type to use it both X and A are the same * */ template<class X, class A, class B> struct Substitute : Tag<X> {}; template<class X, class A, class B> using SubstituteType = typename Substitute<X,A,B>::type; template<class A, class B> struct Substitute<A,A,B> : Tag<B> {}; template<class X, class A, class B> struct Substitute<X&,A,B> : Tag<SubstituteType<X,A,B>&> {}; template<class X, class A, class B> struct Substitute<X&&,A,B> : Tag<SubstituteType<X,A,B>&&> {}; template<class X, class A, class B> struct Substitute<X const,A,B> : Tag<SubstituteType<X,A,B>const> {}; template<class X, class A, class B> struct Substitute<X volatile,A,B> : Tag<SubstituteType<X,A,B>volatile> {}; template<class X, class A, class B> struct Substitute<X const volatile,A,B> : Tag<SubstituteType<X,A,B>const volatile> {}; template<template<class...>class Z,class...Xs, class A, class B> struct Substitute<Z<Xs...>,A,B> : Tag<Z<SubstituteType<Xs,A,B>...>> {}; template<template<class,size_t>class Z,class X,size_t n, class A, class B> struct Substitute<Z<X,n>,A,B> : Tag<Z<SubstituteType<X,A,B>,n>> {}; template<class R,class...Xs, class A, class B> struct Substitute<R(Xs...),A,B> : Tag<SubstituteType<R,A,B>(SubstituteType<Xs,A,B>...)> {}; /** * * \} * */ /** \brief Used as a placeholder when a template type needs to refer to itself * */ struct OwnType {}; /** \brief Wrapper type to allow a Variant type within a class to hold types of that class * */ template<typename T> class RecursiveWrapper; /** \brief Variant type that can hold any value, including itself. * Values are stored on the stack, not the heap, so this should be relatively efficient. * * \tparam Ts A list of unique types, in any order, that this variant is capable of holding. Internally, this sets the * storage and alignment of the internal memory. * */ template<typename... Ts> class Variant; /** \brief Internal class used to iterate through a Variants possible types until a match is found, and then execute the * desired function. * * \tparam T0 The type we are going to test to see if the current stored value is of this type * \tparam Ts The other types not yet checked. * */ template<typename T0, typename... Ts> struct VariantHelper; /** \brief Specialization that allows for the substitution of F with T0 if F is the type of the invoking Variant * * \tparam T0 The invoking Variant type * \tparam F The type we're currently checking * \tparam Ts The other types not yet checked * */ template<typename T0, typename F, typename... Ts> struct VariantHelper<T0, F, Ts...> { inline static std::type_index InvalidType() { return std::type_index(typeid(void)); } /** \brief Calls the destructor on the type * * \param typeIndex The std::type_index of the value we want to destroy * \param data A void* containing the data that will be destroyed * */ inline static void Destroy(const std::type_index typeIndex, void* data) { if(typeIndex == std::type_index(typeid(SubstituteType<F,OwnType,T0>))) { reinterpret_cast<SubstituteType<F,OwnType,T0>*>(data)->~SubstituteType<F,OwnType,T0>(); } else if(typeIndex == InvalidType()) { } else VariantHelper<T0,Ts...>::Destroy(typeIndex, data); } /** \brief Moves the data from oldV into newV * * \param oldTypeIndex Type of the value being moved * \param oldV The old data * \param newV The new data * */ inline static void Move(const std::type_index oldTypeIndex, void* oldV, void* newV) { if(oldTypeIndex == std::type_index(typeid(SubstituteType<F,OwnType,T0>))) new (newV) SubstituteType<F,OwnType,T0>(std::move(*reinterpret_cast<SubstituteType<F,OwnType,T0>*>(oldV))); else VariantHelper<T0,Ts...>::Move(oldTypeIndex,oldV,newV); } /** \brief Copies the data from oldV into newV * * \param oldTypeIndex The type of the value being copied * \param oldV The old data * \param newV The new data * */ inline static void Copy(const std::type_index oldTypeIndex, const void* oldV, void* newV) { if(oldTypeIndex == std::type_index(typeid(SubstituteType<F,OwnType,T0>))) new (newV) SubstituteType<F,OwnType,T0>(*reinterpret_cast<const SubstituteType<F,OwnType,T0>*>(oldV)); else VariantHelper<T0,Ts...>::Copy(oldTypeIndex,oldV,newV); } /** \brief Checks if the provided types is one of the types available in the template params * * \param typeToCheck The type to check if valid * \return true when allowed, false otherwise * */ inline static bool TypeIsValid(const std::type_index typeToCheck) { if(typeToCheck == std::type_index(typeid(SubstituteType<F,OwnType,T0>))) return true; else return VariantHelper<T0,Ts...>::TypeIsValid(typeToCheck); } inline static bool TypeIsValidRecursiveCheck(const std::type_index recursiveWrapperType) { if(recursiveWrapperType == std::type_index(typeid(SubstituteType<F,OwnType,T0>))) return true; else return VariantHelper<T0,Ts...>::TypeIsValidRecursiveCheck(recursiveWrapperType); } }; /** \brief Catchall specialization to allow for void type * * Possibly should throw a runtime_error if we reach these functions to indicate that I'm not very good at C++ * * \tparam T0 the final type * */ template<typename T0> struct VariantHelper<T0> { inline static void Destroy(const std::type_index typeIndex, void* data) {} inline static void Move(const std::type_index oldTypeIndex, void* oldV, void* newV) {} inline static void Copy(const std::type_index oldTypeIndex, const void* oldV, void* newV) {} inline static bool TypeIsValid(const std::type_index typeToCheck) { return false; } inline static bool TypeIsValidRecursiveCheck(const std::type_index recursiveWrapperType) { return false; } }; template<typename... Ts> class Variant { public: /** \brief Default constructor initiates the Variant with a void state * */ Variant() : m_typeIndex(InvalidType()){} /** \brief Copy constructor makes a copy of the data from old in this. * * \param old The variant to copy * */ Variant(const Variant<Ts...>& old) : m_typeIndex(old.m_typeIndex) { HelperType::Copy(old.m_typeIndex, &old.m_data, &m_data); } /** \brief Move constructor transfers the data from old into this. * * \param old The Variant to move * */ Variant(Variant<Ts...>&& old) : m_typeIndex(old.m_typeIndex) { HelperType::Move(old.m_typeIndex, &old.m_data, &m_data); } /** \brief Value constructor will initiate the variant with the T value. * * \tparam T The type of value; must be one of the Variants templated types. * \param value The T value to initiate the Variant with. * */ template<typename T> Variant(const T& value) : m_typeIndex(InvalidType()) { Set<T>(value); } /** \brief Copy and Swap Assignment operator which copies the content old of old into this * * \param old The data to assign to this * */ Variant<Ts...>& operator=(Variant<Ts...> old) { Variant<Ts...> temp(*this); m_typeIndex = old.m_typeIndex; HelperType::Copy(old.m_typeIndex,&old.m_data,&m_data); old.m_typeIndex = temp.m_typeIndex; HelperType::Copy(temp.m_typeIndex,&temp.m_data,&old.m_data); return *this; } /** \brief Helper method to check if the Variant is currently holding a value of type T * * \tparam T The type we want to check if the Variant holds * \return true is the types match, false otherwise * */ template<typename T> bool Is() const { return (m_typeIndex == std::type_index(typeid(T))); } /** \brief Helper method to check if the Variant is holding a void type * * \return true if non-void, false otherwise * */ bool IsValid() const { return (m_typeIndex != std::type_index(typeid(InvalidType()))); } /** \brief Returns the std::type_index currently being held in the Variant * * \return The type being held * */ std::type_index GetTypeIndex() const { return m_typeIndex; } /** \brief Assignment operator of a type * * \tparam T The type we're trying to assign. Must be one of the Variants templated types * \param rhs The value * \return Reference to this * */ template<typename T> Variant<Ts...>& operator=(const T& rhs) { Set<T>(rhs); return *this; } /** \brief Sets the storage of the Variant to the value provided. * * \tparam T The type we're giving the variant * \tparam Args The types of the constructor arguments for type T * \param args The arguments to construct a T value * */ template<typename T, typename... Args> void Set(Args&&... args) { if(!HelperType::TypeIsValid(std::type_index(typeid(T))) && !HelperType::TypeIsValidRecursiveCheck(std::type_index(typeid(RecursiveWrapper<T>)))) throw std::logic_error("Type not supported in Variant template (" + std::string(typeid(T).name()) + ")"); if(m_typeIndex != InvalidType()) { HelperType::Destroy(m_typeIndex,&m_data); m_typeIndex = InvalidType(); } if(!HelperType::TypeIsValidRecursiveCheck(std::type_index(typeid(RecursiveWrapper<T>)))) { new (&m_data) T(std::forward<Args>(args)...); m_typeIndex = std::type_index(typeid(T)); } else { new (&m_data) RecursiveWrapper<T>(std::forward<Args>(args)...); m_typeIndex = std::type_index(typeid(RecursiveWrapper<T>)); } } /** \brief Casts the Variant to type T * * \tparam T The type to cast to * \return A value of type T if the Variant holds a value of T. Otherwise an exception is thrown. * * \exception std::bad_cast The Variant does not hold a value of T * */ template<typename T> operator T() const { return Get<T>(); } /** \brief Returns a const reference to the internal value * * \return A value of type T * * \exception std::bad_cast The Variant does not hold a value of T * */ template<typename T> const T& Get() const { if(m_typeIndex == std::type_index(typeid(T))) return *reinterpret_cast<const T*>(&m_data); else if(m_typeIndex == std::type_index(typeid(RecursiveWrapper<T>))) return (*reinterpret_cast<const RecursiveWrapper<T>*>(&m_data)).Get(); else throw std::bad_cast(); } /** \brief Returns a reference to the internal value * * \tparam T the type you expect the Variant to hold * * \return A reference of type T of the internal value * * \exception std::bad_cast The Variant does not hold a value of type T * */ template<typename T> T& Get() { if(m_typeIndex == std::type_index(typeid(T))) return *reinterpret_cast<T*>(&m_data); else if(m_typeIndex == std::type_index(typeid(RecursiveWrapper<T>))) return (*reinterpret_cast<RecursiveWrapper<T>*>(&m_data)).Get(); else throw std::bad_cast(); } /** \brief Invokes the helper class to invoke the correct destructor on the internal type* */ ~Variant() { HelperType::Destroy(m_typeIndex,&m_data); } private: using DataType = typename std::aligned_union<1,Ts...>::type; /**< The data type that can store all the templated types */ using HelperType = VariantHelper<Variant<Ts...>,Ts...>; /**< Helper type */ /** \brief Provides the std::type_index of a void type * */ static inline std::type_index InvalidType() { return std::type_index(typeid(void)); } std::type_index m_typeIndex; /**< The std::type_index currently being held by the Variant */ DataType m_data; /**< The storage */ }; template<typename T> class RecursiveWrapper { public: RecursiveWrapper() {} ~RecursiveWrapper() { } RecursiveWrapper(const T& t) : m_data(std::make_unique<T>(t)) {} RecursiveWrapper(T&& t) : m_data(std::make_unique<T>(std::move(t))) {} RecursiveWrapper& operator=(T& rhs) { m_data = std::make_unique<T>(rhs); return *this; } RecursiveWrapper(const RecursiveWrapper& r) : m_data(std::make_unique<T>(r.Get())) {} RecursiveWrapper(RecursiveWrapper&& r) : m_data(std::move(r.m_data)) {} RecursiveWrapper& operator=(RecursiveWrapper rhs) { std::swap(*this,rhs); return *this; } operator T() const { return *m_data; } T& Get() const { return *m_data; } private: std::unique_ptr<T> m_data; }; } #endif // VARIANT_HPP_INCLUDED |
I’ll probably do a write up on this at some point. I like it over the boost::variant primarily because I don’t want a never-empty guarantee. I can’t imagine why I’d want a type that doesn’t allow for null-states.
EDIT: Fixed up some of that code.
EDIT 20/05/2016: Fixed up some more of that code.