| 1 |
#ifndef CHILON_VARIANT_HPP |
| 2 |
#define CHILON_VARIANT_HPP |
| 3 |
|
| 4 |
#include <chilon/meta/at.hpp> |
| 5 |
#include <chilon/meta/index_of.hpp> |
| 6 |
#include <chilon/meta/max.hpp> |
| 7 |
#include <chilon/meta/return.hpp> |
| 8 |
#include <chilon/meta/void.hpp> |
| 9 |
#include <chilon/meta/call_type.hpp> |
| 10 |
#include <chilon/meta/contains.hpp> |
| 11 |
#include <chilon/meta/require.hpp> |
| 12 |
|
| 13 |
#include <chilon/singleton.hpp> |
| 14 |
#include <chilon/hash.hpp> |
| 15 |
|
| 16 |
#include <boost/ptr_container/ptr_array.hpp> |
| 17 |
|
| 18 |
#include <utility> |
| 19 |
#include <memory> |
| 20 |
|
| 21 |
#include <assert.h> |
| 22 |
|
| 23 |
namespace chilon { |
| 24 |
|
| 25 |
/// Thrown when a variant is initalised from another variant which |
| 26 |
/// current stores a type that the lhs variant cannot store. |
| 27 |
struct invalid_variant_initalisation {}; |
| 28 |
|
| 29 |
/** |
| 30 |
* @brief A simple variant which can store one of any of the types in |
| 31 |
* T on the heap. |
| 32 |
* @detailed When it is constructed the variant is empty by default. |
| 33 |
* A verison of the variant that stores data on the stack can be |
| 34 |
* enabled by defining CHILON_VARIANT_USES_STACK. This version |
| 35 |
* can never be empty and constructs the first type when default |
| 36 |
* constructed. This version is less convenient to use in places |
| 37 |
* where only forward declarations of the variant types are |
| 38 |
* available. |
| 39 |
* @tparam T pack of valid types the variant may store. |
| 40 |
*/ |
| 41 |
template <class... T> |
| 42 |
struct variant { |
| 43 |
enum { n_elements = sizeof...(T) }; |
| 44 |
#ifdef CHILON_VARIANT_USES_STACK |
| 45 |
enum { max_size = meta::max<sizeof(T)...>::value }; |
| 46 |
#else |
| 47 |
private: |
| 48 |
struct copier { |
| 49 |
template <class U> void operator()(U& v) const { v_ = v; } |
| 50 |
void operator()() const { v_.set_empty(); } |
| 51 |
|
| 52 |
copier(variant& v) : v_(v) {} |
| 53 |
variant& v_; |
| 54 |
}; |
| 55 |
|
| 56 |
struct comparator { |
| 57 |
template <class U> bool operator()(U const& v) const { |
| 58 |
return v == v_.at<U>(); |
| 59 |
} |
| 60 |
bool operator()() const { return v_.empty(); } |
| 61 |
|
| 62 |
comparator(variant const& v) : v_(v) {} |
| 63 |
variant const& v_; |
| 64 |
}; |
| 65 |
|
| 66 |
public: |
| 67 |
bool operator==(variant const& rhs) const { |
| 68 |
return type_index_ == rhs.type_index_ && |
| 69 |
variant_apply(rhs, comparator(*this)); |
| 70 |
}; |
| 71 |
|
| 72 |
bool operator!=(variant const& rhs) const { |
| 73 |
return ! (*this == rhs); |
| 74 |
}; |
| 75 |
|
| 76 |
template <class... U> |
| 77 |
variant& operator=(variant<U...> const& rhs) { |
| 78 |
variant_apply(rhs, copier(*this)); |
| 79 |
return *this; |
| 80 |
} |
| 81 |
|
| 82 |
variant& operator=(variant&& rhs) { |
| 83 |
delete_if_not_empty(); |
| 84 |
data_ = rhs.data_; |
| 85 |
type_index_ = rhs.type_index_; |
| 86 |
rhs.data_ = 0; |
| 87 |
return *this; |
| 88 |
} |
| 89 |
|
| 90 |
template <class V> |
| 91 |
variant& operator=(std::auto_ptr<V>& rhs) { |
| 92 |
int const new_index = meta::index_of<V, T...>::value; |
| 93 |
static_assert( |
| 94 |
new_index < n_elements, "type does not exist in variant"); |
| 95 |
delete_if_not_empty(); |
| 96 |
type_index_ = new_index; |
| 97 |
data_ = rhs.release(); |
| 98 |
return *this; |
| 99 |
} |
| 100 |
|
| 101 |
template <class V> |
| 102 |
variant& operator=(V const& rhs) { |
| 103 |
int const new_index = meta::index_of<V, T...>::value; |
| 104 |
static_assert( |
| 105 |
new_index < n_elements, "type does not exist in variant"); |
| 106 |
#ifdef CHILON_VARIANT_USES_STACK |
| 107 |
type_index_ = new_index; |
| 108 |
cast<V>() = rhs; |
| 109 |
#else |
| 110 |
delete_if_not_empty(); |
| 111 |
|
| 112 |
type_index_ = new_index; |
| 113 |
data_ = new V(rhs); |
| 114 |
#endif |
| 115 |
return *this; |
| 116 |
} |
| 117 |
|
| 118 |
template <class V> |
| 119 |
V *release() { |
| 120 |
int const index = meta::index_of<V, T...>::value; |
| 121 |
static_assert( |
| 122 |
index < n_elements, "type does not exist in variant"); |
| 123 |
assert(type_index_ == index); |
| 124 |
V *data = static_cast<V *>(data_); |
| 125 |
data_ = 0; |
| 126 |
return data; |
| 127 |
} |
| 128 |
|
| 129 |
bool empty() const { return ! data_; } |
| 130 |
void set_empty() { |
| 131 |
if (! empty()) { |
| 132 |
variant_apply(*this, deleter()); |
| 133 |
data_ = 0; |
| 134 |
|
| 135 |
int const void_idx = meta::index_of<void, T...>::value; |
| 136 |
if (void_idx < n_elements) type_index_ = void_idx; |
| 137 |
} |
| 138 |
} |
| 139 |
|
| 140 |
private: |
| 141 |
void delete_if_not_empty() { |
| 142 |
if (! empty()) |
| 143 |
variant_apply(*this, deleter()); |
| 144 |
} |
| 145 |
|
| 146 |
public: |
| 147 |
#endif |
| 148 |
|
| 149 |
typedef typename meta::head<T...>::type head_type; |
| 150 |
|
| 151 |
/** |
| 152 |
* Change variant to represent a default constructed V |
| 153 |
*/ |
| 154 |
template <class V> |
| 155 |
V& construct_default() { |
| 156 |
int const new_index = meta::index_of<V, T...>::value; |
| 157 |
static_assert( |
| 158 |
new_index < n_elements, "type does not exist in variant"); |
| 159 |
#ifdef CHILON_VARIANT_USES_STACK |
| 160 |
type_index_ = new_index; |
| 161 |
return *new (data_) V; |
| 162 |
#else |
| 163 |
set_empty(); |
| 164 |
type_index_ = new_index; |
| 165 |
data_ = new V(); |
| 166 |
return cast<V>(); |
| 167 |
#endif |
| 168 |
} |
| 169 |
|
| 170 |
template <class V, CHILON_REQUIRE(meta::is_void<V>)> |
| 171 |
void construct_default() { set_empty(); } |
| 172 |
|
| 173 |
/** |
| 174 |
* Unsafe access to type at index I. |
| 175 |
*/ |
| 176 |
template <int I> |
| 177 |
typename meta::at<I, T...>::type& at() { |
| 178 |
static_assert(I < n_elements, "index too high"); |
| 179 |
#ifndef NDEBUG |
| 180 |
assert(type_index_ == I); |
| 181 |
#endif |
| 182 |
return cast<typename meta::at<I, T...>::type>(); |
| 183 |
} |
| 184 |
|
| 185 |
/** |
| 186 |
* Unsafe const access to type at index I. |
| 187 |
*/ |
| 188 |
template <int I> |
| 189 |
typename meta::at<I, T...>::type const& at() const { |
| 190 |
static_assert(I < n_elements, "type not found in variant"); |
| 191 |
#ifndef NDEBUG |
| 192 |
assert(type_index_ == I); |
| 193 |
#endif |
| 194 |
return cast<typename meta::at<I, T...>::type>(); |
| 195 |
} |
| 196 |
|
| 197 |
template <int I, |
| 198 |
CHILON_REQUIRE(meta::is_void<typename meta::at<I, T...>::type >) > |
| 199 |
void at() const {}; |
| 200 |
|
| 201 |
template <class Y> |
| 202 |
Y& at() { |
| 203 |
return at<meta::index_of<Y, T...>::value>(); |
| 204 |
} |
| 205 |
|
| 206 |
template <class Y> |
| 207 |
Y const& at() const { |
| 208 |
return at<meta::index_of<Y, T...>::value>(); |
| 209 |
} |
| 210 |
|
| 211 |
template <class U> |
| 212 |
bool is() const { |
| 213 |
int const index = meta::index_of<U, T...>::value; |
| 214 |
static_assert(index < n_elements, "type does not exist in variant"); |
| 215 |
return type_index_ == index; |
| 216 |
} |
| 217 |
|
| 218 |
int const type_index() const { return type_index_; } |
| 219 |
|
| 220 |
private: |
| 221 |
// unsafe casts |
| 222 |
template <class Y> |
| 223 |
Y const& cast() const { |
| 224 |
return (*reinterpret_cast<Y const *>(data_)); |
| 225 |
} |
| 226 |
|
| 227 |
template <class Y> |
| 228 |
Y& cast() { |
| 229 |
return (*reinterpret_cast<Y *>(data_)); |
| 230 |
} |
| 231 |
|
| 232 |
public: |
| 233 |
// default constructor constructs the first element in the tuple |
| 234 |
#ifdef CHILON_VARIANT_USES_STACK |
| 235 |
variant() : type_index_(0) { |
| 236 |
new (data_) head_type; |
| 237 |
} |
| 238 |
|
| 239 |
private: |
| 240 |
char data_[max_size]; |
| 241 |
#else |
| 242 |
private: |
| 243 |
struct initialiser { |
| 244 |
template <class U> |
| 245 |
void operator()(U const& v) const { |
| 246 |
v_.data_ = new U(v); |
| 247 |
} |
| 248 |
|
| 249 |
void operator()() const { |
| 250 |
v_.set_empty(); |
| 251 |
} |
| 252 |
|
| 253 |
initialiser(variant& v) : v_(v) {} |
| 254 |
protected: |
| 255 |
variant& v_; |
| 256 |
}; |
| 257 |
|
| 258 |
struct careful_initialiser : initialiser { |
| 259 |
template <class U> |
| 260 |
CHILON_RETURN_REQUIRE(void, meta::contains<U, T...>) |
| 261 |
operator()(U const& v) const { |
| 262 |
this->v_.type_index_ = meta::index_of<U, T...>::value; |
| 263 |
initialiser::operator()(v); |
| 264 |
} |
| 265 |
|
| 266 |
template <class U> |
| 267 |
CHILON_RETURN_NOT_REQUIRE(void, meta::contains<U, T...>) |
| 268 |
operator()(U const& v) const { |
| 269 |
throw invalid_variant_initalisation(); |
| 270 |
} |
| 271 |
|
| 272 |
careful_initialiser(variant& v) : initialiser(v) {} |
| 273 |
}; |
| 274 |
|
| 275 |
struct deleter { |
| 276 |
template <class U> void operator()(U& v) const { delete &v; } |
| 277 |
void operator()() const {} |
| 278 |
}; |
| 279 |
|
| 280 |
public: |
| 281 |
variant() : data_(0), type_index_(0) { |
| 282 |
construct_default< typename meta::head<T...>::type >(); |
| 283 |
} |
| 284 |
|
| 285 |
variant(variant const& rhs) : type_index_(rhs.type_index_) { |
| 286 |
variant_apply(rhs, initialiser(*this)); |
| 287 |
} |
| 288 |
|
| 289 |
variant(variant&& rhs) |
| 290 |
: data_(rhs.data_), type_index_(rhs.type_index_) |
| 291 |
{ rhs.data_ = 0; } |
| 292 |
|
| 293 |
template <class U, |
| 294 |
CHILON_REQUIRE_I(meta::index_of<U, T...>::value < n_elements)> |
| 295 |
variant(U const& u) |
| 296 |
: data_(new U(u)), |
| 297 |
type_index_(meta::index_of<U, T...>::value) |
| 298 |
{} |
| 299 |
|
| 300 |
template <class U, |
| 301 |
CHILON_REQUIRE_I(meta::index_of<U, T...>::value < n_elements)> |
| 302 |
variant(U&& u) |
| 303 |
: data_(new U(std::move(u))), |
| 304 |
type_index_(meta::index_of<U, T...>::value) |
| 305 |
{} |
| 306 |
|
| 307 |
// TODO: check variant is not subvariant first, then handle |
| 308 |
// it like a regular member type |
| 309 |
template <class... U> |
| 310 |
variant(variant<U...> const& rhs) { |
| 311 |
variant_apply(rhs, careful_initialiser(*this)); |
| 312 |
} |
| 313 |
|
| 314 |
~variant() { set_empty(); } |
| 315 |
|
| 316 |
template <class U> |
| 317 |
variant(std::auto_ptr<U>& rhs) |
| 318 |
: type_index_(meta::index_of<U, T...>::value) |
| 319 |
{ |
| 320 |
static_assert( |
| 321 |
meta::index_of<U, T...>::value < n_elements, |
| 322 |
"type does not exist in variant"); |
| 323 |
data_ = rhs.release(); |
| 324 |
} |
| 325 |
protected: |
| 326 |
void *data_; |
| 327 |
#endif |
| 328 |
int type_index_; |
| 329 |
}; |
| 330 |
|
| 331 |
#if 0 && ! defined(CHILON_VARIANT_USES_STACK) |
| 332 |
/** |
| 333 |
* @brief If a void entry is used as the first entry in a variant, then |
| 334 |
* the variant can be empty. The void entry is not considered part |
| 335 |
* of the variant in the indexing system. |
| 336 |
*/ |
| 337 |
template <class... T> |
| 338 |
struct variant<void, T...> : variant<T...> { |
| 339 |
typedef variant<T...> base_t; |
| 340 |
|
| 341 |
template <class U> |
| 342 |
variant<void, T...>& operator=(U&& rhs) { |
| 343 |
base_t::operator=(std::move<U>(rhs)); |
| 344 |
} |
| 345 |
|
| 346 |
variant() : base_t() {} |
| 347 |
|
| 348 |
template <class U> |
| 349 |
variant(U&& rhs) : base_t(std::forward<U>(rhs)) {} |
| 350 |
}; |
| 351 |
#endif |
| 352 |
|
| 353 |
namespace detail { |
| 354 |
template <class T, class F> |
| 355 |
struct variant_apply_helper {}; |
| 356 |
|
| 357 |
template <class F, class T> |
| 358 |
struct variant_call_type; |
| 359 |
|
| 360 |
template <class F, class... T> |
| 361 |
struct variant_call_type<F, variant<T...> > : |
| 362 |
meta::call_type_ref<F, typename meta::at<0, T...>::type> {}; |
| 363 |
|
| 364 |
template <class F, class... T> |
| 365 |
struct variant_apply_lookup_table { |
| 366 |
typedef variant<T...> type; |
| 367 |
|
| 368 |
struct apply { |
| 369 |
virtual typename variant_call_type<F, type>::type |
| 370 |
operator()(type const& v, F const& f) =0; |
| 371 |
|
| 372 |
virtual typename variant_call_type<F, type>::type |
| 373 |
operator()(type& v, F const& f) =0; |
| 374 |
}; |
| 375 |
|
| 376 |
boost::ptr_array<apply, type::n_elements> appliers_; |
| 377 |
|
| 378 |
template <int I, class U> |
| 379 |
struct apply_idx : apply { |
| 380 |
virtual auto operator()(type const& v, F const& f) |
| 381 |
CHILON_RETURN(f(v.template at<I>())) |
| 382 |
virtual auto operator()(type& v, F const& f) |
| 383 |
CHILON_RETURN(f(v.template at<I>())) |
| 384 |
}; |
| 385 |
|
| 386 |
template <int I> |
| 387 |
struct apply_idx<I, void> : apply { |
| 388 |
virtual auto operator()(type const& v, F const& f) |
| 389 |
CHILON_RETURN(f()) |
| 390 |
|
| 391 |
virtual auto operator()(type& v, F const& f) |
| 392 |
CHILON_RETURN(f()) |
| 393 |
}; |
| 394 |
|
| 395 |
template <int I, int L> |
| 396 |
struct create_applier { |
| 397 |
static void exec(decltype(appliers_)& appliers) { |
| 398 |
appliers.replace( |
| 399 |
I, |
| 400 |
new apply_idx<I, typename meta::at<I, T...>::type >() |
| 401 |
); |
| 402 |
create_applier<I + 1, L>::exec(appliers); |
| 403 |
} |
| 404 |
}; |
| 405 |
|
| 406 |
template <int I> |
| 407 |
struct create_applier<I, I> { |
| 408 |
static void exec(decltype(appliers_)& appliers) {} |
| 409 |
}; |
| 410 |
|
| 411 |
variant_apply_lookup_table() { |
| 412 |
create_applier<0, type::n_elements>::exec(appliers_); |
| 413 |
} |
| 414 |
}; |
| 415 |
|
| 416 |
template <class... T, class F> |
| 417 |
struct variant_apply_helper< variant<T...>, F > { |
| 418 |
typedef variant<T...> type; |
| 419 |
|
| 420 |
inline static typename variant_call_type<F, type>::type |
| 421 |
exec(type const& v, F const& f) { |
| 422 |
return chilon::singleton< |
| 423 |
variant_apply_lookup_table<F, T...> |
| 424 |
>::instance().appliers_[v.type_index()](v, f); |
| 425 |
} |
| 426 |
|
| 427 |
inline static typename variant_call_type<F, type>::type |
| 428 |
exec(type& v, F const& f) { |
| 429 |
return chilon::singleton< |
| 430 |
variant_apply_lookup_table<F, T...> |
| 431 |
>::instance().appliers_[v.type_index()](v, f); |
| 432 |
} |
| 433 |
|
| 434 |
inline static typename variant_call_type<F, type>::type |
| 435 |
exec(int const idx, type const& v, F const& f) { |
| 436 |
return chilon::singleton< |
| 437 |
variant_apply_lookup_table<F, T...> |
| 438 |
>::instance().appliers_[idx](v, f); |
| 439 |
} |
| 440 |
|
| 441 |
inline static typename variant_call_type<F, type>::type |
| 442 |
exec(int const idx, type& v, F const& f) { |
| 443 |
return chilon::singleton< |
| 444 |
variant_apply_lookup_table<F, T...> |
| 445 |
>::instance().appliers_[idx](v, f); |
| 446 |
} |
| 447 |
}; |
| 448 |
} |
| 449 |
|
| 450 |
template <class F, class T> |
| 451 |
inline auto variant_apply(T& v, F const& f) |
| 452 |
CHILON_RETURN(detail::variant_apply_helper<T, F>::exec(v, f)) |
| 453 |
|
| 454 |
template <class F, class T> |
| 455 |
inline auto variant_apply(T const& v, F const& f) |
| 456 |
CHILON_RETURN(detail::variant_apply_helper<T, F>::exec(v, f)) |
| 457 |
|
| 458 |
template <class F, class T> |
| 459 |
inline auto variant_construct_apply(int const idx, T const& v, F const& f) |
| 460 |
CHILON_RETURN(detail::variant_apply_helper<T, F>::exec(idx, v, f)) |
| 461 |
|
| 462 |
template <class F, class T> |
| 463 |
inline auto variant_construct_apply(int const idx, T& v, F const& f) |
| 464 |
CHILON_RETURN(detail::variant_apply_helper<T, F>::exec(idx, v, f)) |
| 465 |
|
| 466 |
template <class T, class... Y> |
| 467 |
inline T& variant_as(variant<Y...>& v) { |
| 468 |
return v.template at<T>(); |
| 469 |
} |
| 470 |
|
| 471 |
template <class T, class... Y> |
| 472 |
inline T& variant_as(variant<Y...> const& v) { |
| 473 |
return v.template at<T>(); |
| 474 |
} |
| 475 |
|
| 476 |
template <class T> |
| 477 |
inline T& variant_as(T& v) { return v; } |
| 478 |
|
| 479 |
template <class T> |
| 480 |
inline T const& variant_as(T const& v) { return v; } |
| 481 |
|
| 482 |
/** |
| 483 |
* call construct_default on a variant. |
| 484 |
*/ |
| 485 |
template <class T, class... Y> |
| 486 |
inline void construct_default(variant<Y...>& v) { |
| 487 |
v.template construct_default<T>(); |
| 488 |
} |
| 489 |
|
| 490 |
/** |
| 491 |
* construct_default on non-variant does nothing |
| 492 |
*/ |
| 493 |
template <class T> |
| 494 |
inline void construct_default(T const& v) {} |
| 495 |
|
| 496 |
struct hash_variant { |
| 497 |
size_t operator()() const { return 0; } |
| 498 |
|
| 499 |
template <class T> |
| 500 |
size_t operator()(T const& t) const { return chilon::hash(t); } |
| 501 |
}; |
| 502 |
|
| 503 |
/** |
| 504 |
* register with boost::hash |
| 505 |
*/ |
| 506 |
template <class... T> |
| 507 |
std::size_t inline hash_value(variant<T...> const& t) { |
| 508 |
return variant_apply(t, hash_variant()); |
| 509 |
} |
| 510 |
|
| 511 |
} |
| 512 |
|
| 513 |
namespace std { |
| 514 |
template <class... T> |
| 515 |
struct hash<chilon::variant<T...>> { |
| 516 |
std::size_t operator()(chilon::variant<T...> const& arg) const { |
| 517 |
return chilon::hash_value(arg); |
| 518 |
} |
| 519 |
}; |
| 520 |
} |
| 521 |
|
| 522 |
#endif |