├── .gitignore ├── README.md ├── test-adt.cpp ├── test-variant.cpp └── variant.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | session.vim 3 | *.swp 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weekend project: type-safe, memory-managed sum-types in C++11 2 | 3 | Although C++11 provides unrestricted unions, repetitive boilerplate code is needed to 4 | aggregate classes with non-trivial constructors/destructors. 5 | [variant.hpp] (https://github.com/apunktbau/variant/tree/master/variant.hpp) provides type-safe, 6 | memory-managed sum-types using variadic templated unions. 7 | 8 | Variant variant; 9 | 10 | defines a sum-type over `bool`, `int` and `std::string`. 11 | The types of a `Variant` may have non-trivial, parametrized constructors. 12 | The only requirement is a copy-constructor. 13 | 14 | There are two ways to set a value. 15 | By index 16 | 17 | variant.setAt <2> (std::string ("hello world")); 18 | 19 | or by type 20 | 21 | variant.set (std::string ("hello world")); 22 | 23 | . Both methods are statically type-safe. 24 | To differentiate between values of the same type `setAt` must be used. 25 | 26 | Call `T& get ` to retrieve a value from a variant: 27 | 28 | variant.get (); 29 | 30 | `get ` results in a run-time error if the variant holds a value of a different type. 31 | 32 | Call `bool is ` to check against the currently stored type: 33 | 34 | if (variant.is ()) { ... } 35 | 36 | `U caseOf ` provides implicit type matching: it expects a function for each 37 | value a variant can store. 38 | Depending on the variant's current value, the corresponding function is executed: 39 | 40 | variant.caseOf ( 41 | [] (bool&) { /* do something with bool */ return 0; } 42 | , [] (int&) { /* do something with int */ return 1; } 43 | , [] (std::string&) { /* do something with string */ return 2; } 44 | ) 45 | 46 | The `Variant` template is memory-managed: the currently allocated value is deleted if 47 | the variant's destructor is called. 48 | `void release ()` deletes the current value explicitly: 49 | 50 | variant.release (); 51 | 52 | See [test-variant.cpp] (https://github.com/apunktbau/variant/tree/master/test-variant.cpp) 53 | for more code. 54 | 55 | ## Fake Algebraic Data Types ## 56 | 57 | Consider the following definitions in Haskell: 58 | 59 | data List a = Nil | Cons a (List a) 60 | 61 | length list = case list of 62 | Nil -> 0 63 | Cons x xs -> 1 + (length xs) 64 | 65 | Let's emulate this in C++11 by using `Variant`. 66 | In order to represent a Haskell constructor without arguments, we introduce a `Unit` type: 67 | 68 | struct Unit {}; 69 | 70 | `List` becomes a template with one argument: 71 | 72 | template 73 | struct List { 74 | 75 | For brevity, we define a type alias for each constructor: 76 | 77 | typedef Unit Nil; 78 | typedef std::tuple > Cons; 79 | 80 | Note that we use `std::tuple` to emulate Haskell constructors with more than one argument. 81 | 82 | Variant constructor; 83 | 84 | defines the actual sum type. 85 | 86 | For convinience, we define a static function for each constructor. 87 | 88 | static List nil () { 89 | List l; 90 | l.constructor.template set (Nil ()); 91 | return l; 92 | } 93 | 94 | builds an empty list and 95 | 96 | static List cons (const t& x, const List& xs) { 97 | List l; 98 | l.constructor.template set (Cons (x,xs)); 99 | return l; 100 | } 101 | 102 | wraps the `Cons` constructor. 103 | 104 | We define a `length` function on `List `: 105 | 106 | template 107 | unsigned int length (const List & list) { 108 | 109 | We use `Variant`'s implicit type matching: 110 | 111 | return list.constructor.template caseOf ( 112 | 113 | The base case is rather simple: 114 | 115 | [] (typename List ::Nil&) { 116 | return 0; 117 | } 118 | 119 | We simple return constant `0` if an empty list was passed. 120 | In the recursive case we extract the arguments `x` and `xs` of the `Cons` constructor and 121 | call `length` recursively. 122 | 123 | , [] (typename List ::Cons& arguments) { 124 | int& x = std::get <0> (arguments); 125 | List & xs = std::get <1> (arguments); 126 | 127 | return 1 + length (xs); 128 | } 129 | 130 | Now we can call `length` on actual lists: 131 | 132 | int main () { 133 | 134 | List list = List::cons (10, List ::cons (20, List ::nil ())); 135 | 136 | assert (length (list) == 2); 137 | 138 | return 0; 139 | } 140 | 141 | See [test-adt.cpp] (https://github.com/apunktbau/variant/tree/master/test-adt.cpp) 142 | for the full code. 143 | 144 | ## Other approaches 145 | 146 | - [Boost.Variant] (http://www.boost.org/doc/libs/1_55_0/doc/html/variant.html) 147 | implements type-safe variants using visitor classes for functions on variants. 148 | -------------------------------------------------------------------------------- /test-adt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "variant.hpp" 4 | 5 | struct Unit {}; 6 | 7 | template 8 | struct List { 9 | typedef Unit Nil; 10 | typedef std::tuple > Cons; 11 | 12 | Variant constructor; 13 | 14 | static List nil () { 15 | List l; 16 | l.constructor.template set (Nil ()); 17 | return l; 18 | } 19 | 20 | static List cons (const t& x, const List& xs) { 21 | List l; 22 | l.constructor.template set (Cons (x,xs)); 23 | return l; 24 | } 25 | }; 26 | 27 | template 28 | unsigned int length (List & list) { 29 | return list.constructor.template caseOf ( 30 | 31 | [] (typename List ::Nil&) { 32 | return 0; 33 | } 34 | 35 | , [] (typename List ::Cons& arguments) { 36 | int& x = std::get <0> (arguments); 37 | List & xs = std::get <1> (arguments); 38 | 39 | return 1 + length (xs); 40 | } 41 | ); 42 | } 43 | 44 | int main () { 45 | 46 | List list = List::cons (10, List ::cons (20, List ::nil ())); 47 | 48 | assert (length (list) == 2); 49 | 50 | return 0; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /test-variant.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "variant.hpp" 3 | 4 | class Foo { 5 | public: 6 | Foo (int) {} // non-trivial constructor 7 | }; 8 | 9 | class Bar {}; 10 | 11 | int main () { 12 | Variant variant; 13 | 14 | variant.setAt <0> (Foo (5)); // set by index 15 | variant.setAt <2> (42); // set by index 16 | variant.setAt <3> (std::string ("hallo welt")); // set by index 17 | 18 | variant.set (Foo (5)); // set by type 19 | variant.set (42); // set by type 20 | variant.set (std::string ("hello world"));// set by type 21 | 22 | variant.init (); // initialize by type (using default ctor) 23 | variant.init (); // initialize by type (using default ctor) 24 | 25 | variant.get (); // gives std::string& 26 | //variant.get (); // runtime error 27 | 28 | assert (variant.is () == false); // test by type 29 | assert (variant.is () == false); // test by type 30 | assert (variant.is () == true); // test by type 31 | 32 | assert ( variant.caseOf ( // implicit type matching 33 | [] (Foo&) { return false; } 34 | , [] (Bar&) { return false; } 35 | , [] (int&) { return false; } 36 | , [] (std::string&) { return true ; } 37 | ) 38 | ); 39 | 40 | //variant.set (false); // compilation fails (bool is not in variant) 41 | //variant.setAt <3> (12); // compilation fails (incompatible types) 42 | //variant.setAt <5> (12); // compilation fails (index out of bounds) 43 | 44 | Variant variant2 (variant); // (deep) copy construction 45 | 46 | assert (variant2.is () == true); 47 | 48 | Variant variant3; 49 | 50 | variant3 = variant2; // (deep) assignment 51 | 52 | variant2.release (); // manual release 53 | 54 | assert (variant3.is () == true); 55 | 56 | return 0; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /variant.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VARIANT_35425680_d10a_41c7_b91e_2e48cfe545da 2 | #define VARIANT_35425680_d10a_41c7_b91e_2e48cfe545da 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // `VariantDetails` encapsulates internal details of the variant implementation 9 | namespace VariantDetails { 10 | 11 | template 12 | union VariantUnion; 13 | 14 | // Utility structure to initialize value in a union using its default constructor 15 | template 16 | struct InitValue; 17 | 18 | template 19 | struct InitValue <0,U,T,Ts ...> { 20 | static void run (VariantUnion & variant) { 21 | static_assert (std::is_same ::value, "variant type mismatch"); 22 | variant.t = new U (); 23 | } 24 | }; 25 | 26 | template 27 | struct InitValue { 28 | static void run (VariantUnion & variant) { 29 | InitValue :: run (variant.ts); 30 | } 31 | }; 32 | 33 | // Utility structure to set value in a union 34 | template 35 | struct SetValue; 36 | 37 | template 38 | struct SetValue <0,U,T,Ts ...> { 39 | static void run (VariantUnion & variant, const U& u) { 40 | static_assert (std::is_same ::value, "variant type mismatch"); 41 | variant.t = new U (u); 42 | } 43 | }; 44 | 45 | template 46 | struct SetValue { 47 | static void run (VariantUnion & variant, const U& u) { 48 | SetValue :: run (variant.ts, u); 49 | } 50 | }; 51 | 52 | // Utility structure to get the type of the i-th variant 53 | template 54 | struct GetType; 55 | 56 | template 57 | struct GetType <0,T,Ts ...> { 58 | typedef T type; 59 | }; 60 | 61 | template 62 | struct GetType { 63 | typedef typename GetType ::type type; 64 | }; 65 | 66 | // Utility structure to get the index of the variant of a certain type 67 | template 68 | struct GetIndex; 69 | 70 | template 71 | struct GetIndex { 72 | static constexpr bool found = std::is_same ::value; 73 | static constexpr unsigned int index = i; 74 | }; 75 | 76 | template 77 | struct GetIndex { 78 | static constexpr bool found = 79 | std::is_same ::value ? true : GetIndex ::found; 80 | 81 | static constexpr unsigned int index = 82 | std::is_same ::value ? i : GetIndex ::index; 83 | }; 84 | 85 | // `VariantUnion ` implements union over pointers of type `T1,T2,...,Tn` 86 | // 87 | // Case n == 1; 88 | template 89 | union VariantUnion { 90 | static_assert ( std::is_object ::value, "non-object types are not supported"); 91 | static_assert (! std::is_const ::value, "const-qualified types are not supported"); 92 | static_assert (! std::is_volatile ::value, "volatile-qualified types are not supported"); 93 | 94 | T* t; 95 | 96 | VariantUnion () : t (nullptr) {} 97 | VariantUnion (const VariantUnion&) = delete; 98 | VariantUnion ( VariantUnion&&) = delete; 99 | 100 | const VariantUnion& operator= (const VariantUnion&) = delete; 101 | const VariantUnion& operator= ( VariantUnion&&) = delete; 102 | 103 | void copy (unsigned int i, const VariantUnion& other) { 104 | assert (i == 0); 105 | (void)i; 106 | this->t = new T (*other.t); 107 | } 108 | 109 | void move (unsigned int i, VariantUnion&& other) { 110 | assert (i == 0); 111 | (void)i; 112 | this->t = other.t; 113 | other.t = nullptr; 114 | } 115 | 116 | void release (unsigned int i) { 117 | assert (i == 0); 118 | (void)i; 119 | delete this->t; 120 | } 121 | 122 | template 123 | void init () { 124 | InitValue :: run (*this); 125 | } 126 | 127 | template 128 | void set (const U& u) { 129 | SetValue :: run (*this, u); 130 | } 131 | 132 | template 133 | U& get (unsigned int i) { 134 | assert (i == 0); 135 | assert ((std::is_same ::value)); 136 | (void)i; 137 | return *reinterpret_cast (this->t); 138 | } 139 | 140 | template 141 | const U& get (unsigned int i) const { 142 | assert (i == 0); 143 | assert ((std::is_same ::value)); 144 | (void)i; 145 | return *reinterpret_cast (this->t); 146 | } 147 | 148 | template 149 | U caseOf (unsigned int i, const std::function & branch) const { 150 | assert (i == 0); 151 | (void)i; 152 | return branch (*this->t); 153 | } 154 | }; 155 | 156 | // Case n > 1; 157 | template 158 | union VariantUnion { 159 | static_assert ( std::is_object ::value, "non-object types are not supported"); 160 | static_assert (! std::is_const ::value, "const-qualified types are not supported"); 161 | static_assert (! std::is_volatile ::value, "volatile-qualified types are not supported"); 162 | 163 | T* t; 164 | VariantUnion ts; 165 | 166 | VariantUnion () : t (nullptr) {} 167 | VariantUnion (const VariantUnion&) = delete; 168 | VariantUnion ( VariantUnion&&) = delete; 169 | 170 | const VariantUnion& operator= (const VariantUnion&) = delete; 171 | const VariantUnion& operator= ( VariantUnion&&) = delete; 172 | 173 | void copy (unsigned int i, const VariantUnion& other) { 174 | if (i == 0) { 175 | this->t = new T (*other.t); 176 | } 177 | else { 178 | this->ts.copy (i-1, other.ts); 179 | } 180 | } 181 | 182 | void move (unsigned int i, VariantUnion&& other) { 183 | if (i == 0) { 184 | this->t = other.t; 185 | other.t = nullptr; 186 | } 187 | else { 188 | this->ts.move (i-1, std::move (other.ts)); 189 | } 190 | } 191 | 192 | void release (unsigned int i) { 193 | if (i == 0) { 194 | delete this->t; 195 | } 196 | else { 197 | this->ts.release (i-1); 198 | } 199 | } 200 | 201 | template 202 | void init () { 203 | InitValue :: run (*this); 204 | } 205 | 206 | template 207 | void set (const U& u) { 208 | SetValue :: run (*this, u); 209 | } 210 | 211 | template 212 | U& get (unsigned int i) { 213 | if (i == 0) { 214 | assert ((std::is_same::value)); 215 | return *reinterpret_cast (this->t); 216 | } 217 | else { 218 | return this->ts.template get (i-1); 219 | } 220 | } 221 | 222 | template 223 | const U& get (unsigned int i) const { 224 | if (i == 0) { 225 | assert ((std::is_same::value)); 226 | return *reinterpret_cast (this->t); 227 | } 228 | else { 229 | return this->ts.template get (i-1); 230 | } 231 | } 232 | 233 | template 234 | U caseOf (unsigned int i, const std::function & branch 235 | , const std::function & ... branches) const { 236 | if (i == 0) { 237 | return branch (*this->t); 238 | } 239 | else { 240 | return this->ts.template caseOf (i-1, branches ...); 241 | } 242 | } 243 | }; 244 | }; 245 | 246 | // `Variant` wraps `VariantUnion` and handles construction, access and destruction 247 | template 248 | class Variant { 249 | public: 250 | Variant () 251 | : _varUnion () 252 | , _isSet (false) 253 | , _setTo (0) 254 | {} 255 | 256 | Variant (const Variant& other) 257 | : _varUnion () 258 | , _isSet (other._isSet) 259 | , _setTo (other._setTo) 260 | { 261 | if (this->isSet ()) { 262 | this->_varUnion.copy (this->_setTo, other._varUnion); 263 | } 264 | } 265 | 266 | Variant (Variant&& other) 267 | : _varUnion () 268 | , _isSet (other._isSet) 269 | , _setTo (other._setTo) 270 | { 271 | other._isSet = false; 272 | 273 | if (this->isSet ()) { 274 | this->_varUnion.move (this->_setTo, std::move (other._varUnion)); 275 | } 276 | } 277 | 278 | const Variant& operator= (const Variant& other) { 279 | if (this == &other) { 280 | return *this; 281 | } 282 | this->release (); 283 | this->_isSet = other._isSet; 284 | this->_setTo = other._setTo; 285 | 286 | if (this->isSet ()) { 287 | this->_varUnion.copy (this->_setTo, other._varUnion); 288 | } 289 | return *this; 290 | } 291 | 292 | const Variant& operator= (Variant&& other) { 293 | if (this == &other) { 294 | return *this; 295 | } 296 | this->release (); 297 | this->_isSet = other._isSet; 298 | this->_setTo = other._setTo; 299 | 300 | other._isSet = false; 301 | 302 | if (this->isSet ()) { 303 | this->_varUnion.move (this->_setTo, std::move (other._varUnion)); 304 | } 305 | return *this; 306 | } 307 | 308 | ~Variant () { 309 | this->release (); 310 | } 311 | 312 | void release () { 313 | if (this->isSet ()) { 314 | this->_varUnion.release (this->_setTo); 315 | this->_isSet = false; 316 | } 317 | } 318 | 319 | template 320 | U& get () { 321 | assert (this->isSet ()); 322 | return this->_varUnion.template get (this->_setTo); 323 | } 324 | 325 | template 326 | const U& get () const { 327 | assert (this->isSet ()); 328 | return this->_varUnion.template get (this->_setTo); 329 | } 330 | 331 | template > 332 | void initAt () { 333 | this->template resetTo (); 334 | this->_varUnion.template init (); 335 | } 336 | 337 | template 338 | void init () { 339 | constexpr bool found = VariantDetails::GetIndex <0,U,Ts ...>::found; 340 | constexpr unsigned int index = VariantDetails::GetIndex <0,U,Ts ...>::index; 341 | static_assert (found, "variant type not found"); 342 | this->template initAt (); 343 | } 344 | 345 | template > 346 | void setAt (const U& u) { 347 | this->template resetTo (); 348 | this->_varUnion.template set (u); 349 | } 350 | 351 | template 352 | void set (const U& u) { 353 | constexpr bool found = VariantDetails::GetIndex <0,U,Ts ...>::found; 354 | constexpr unsigned int index = VariantDetails::GetIndex <0,U,Ts ...>::index; 355 | static_assert (found, "variant type not found"); 356 | this->template setAt (u); 357 | } 358 | 359 | template 360 | bool is () const { 361 | if (this->isSet ()) { 362 | constexpr bool found = VariantDetails::GetIndex <0,U,Ts ...>::found; 363 | constexpr unsigned int index = VariantDetails::GetIndex <0,U,Ts ...>::index; 364 | static_assert (found, "variant type not found"); 365 | return this->_setTo == index; 366 | } 367 | else { 368 | return false; 369 | } 370 | } 371 | 372 | template 373 | U caseOf (const std::function & ... branches) const { 374 | assert (this->isSet ()); 375 | return this->_varUnion.template caseOf (this->_setTo, branches ...); 376 | } 377 | 378 | bool isSet () const { 379 | return this->_isSet; 380 | } 381 | 382 | private: 383 | VariantDetails::VariantUnion _varUnion; 384 | bool _isSet; 385 | unsigned int _setTo; 386 | 387 | template 388 | void resetTo () { 389 | if (this->isSet ()) { 390 | this->release (); 391 | } 392 | this->_isSet = true; 393 | this->_setTo = i; 394 | } 395 | }; 396 | 397 | #endif 398 | --------------------------------------------------------------------------------