//------------------------------------- // utils::variant: Tagged union in C++ //------------------------------------- // // Copyright kennytm (auraHT Ltd.) 2011. // Boost Software License - Version 1.0 - August 17th, 2003 // Permission is hereby granted, free of charge, to any person or organization // obtaining a copy of the software and accompanying documentation covered by // this license (the "Software") to use, reproduce, display, distribute, // execute, and transmit the Software, and to prepare derivative works of the // Software, and to permit third-parties to whom the Software is furnished to // do so, all subject to the following: // The copyright notices in the Software and this entire statement, including // the above license grant, this restriction and the following disclaimer, // must be included in all copies of the Software, in whole or in part, and // all derivative works of the Software, unless such copies or derivative // works are solely in the form of machine-executable object code generated by // a source language processor. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. /** ```` --- Tagged union ======================================== This module provides the :type:`utils::variant` type, which stores tagged union of objects (also known as discriminated union or sum type). This is mainly a rewrite of `boost::variant `_ for C++11 support. :type:`utils::variant` support the following features over ``boost::variant``: * Proper move semantics * Stricter, compile-time type checking * Visitation using pattern-matching, in additional to the traditional class-based method. but it also has some features not present in :type:`utils::variant`, and will likely not be supported here: * Recursive variant * Reference members * `Boost.MPL `_ * C++03, and support for compilers other than gcc ≥4.7 (and clang when its C++11 support becomes better). Synopsis -------- :: #include #include #include int main() { std::vector v {8, 9, 10, 12, 18, 24, 36, 48, 64, 72, 96}; utils::variant, std::string> u = std::move(v); assert(v.is_type< std::vector >()); utills::case_of(u, [](const std::vector& vec) { for (int val : vec) printf("%d\n", val); }, [](const std::string& str) { printf("[%s]\n", str.c_str()); } ); return 0; } */ #ifndef VARIANT_HPP_XMPYUK9CBNN #define VARIANT_HPP_XMPYUK9CBNN #include #include #include #if !defined(BOOST_NO_TYPEID) #include #endif #include "traits.hpp" namespace utils { template class static_visitor { public: typedef RT result_type; }; template class variant; template struct is_variant { enum { value = false }; }; template struct is_variant> { enum { value = true }; }; } #include "variant-impl.hpp" namespace utils { /** Members ------- */ /** .. type:: class utils::variant final :default_constructible: :movable: :copyable: The tagged union type. */ template class variant { typedef xx_impl::union_impl union_type; size_t _index; union_type _storage; class init_to_visitor : public static_visitor { public: variant* this_; template void operator()(U&& other) { this_->init(std::forward(other)); } }; template void init(U&& other) { typedef xx_impl::get_index index_tmpl; static_assert(index_tmpl::found, "Constructing variant from unexpected type."); static_assert(!index_tmpl::ambiguous, "Constructing variant with ambiguous conversion."); static const size_t index_of_U = index_tmpl::index; _index = index_of_U; xx_impl::init_visitor_1 ctor (std::forward(other)); xx_impl::static_applier()(_storage, ctor); } public: /** .. function:: variant() Construct a new variant type and initialize with the default value of the first type in *T*. */ variant() : _index(0) { new(&_storage.head) decltype(_storage.head); } ~variant() { xx_impl::destroy_visitor dtor; apply(_storage, _index, dtor); } template variant(U&& other) { this->init(std::forward(other)); } template variant(variant& other) { init_to_visitor ctor; ctor.this_ = this; apply(other._storage, other._index, ctor); } template variant(variant&& other) { init_to_visitor ctor; ctor.this_ = this; apply(std::move(other._storage), other._index, ctor); } variant(variant& other) : _index(other._index) { xx_impl::init_visitor_2 ctor; apply(_storage, other._storage, _index, ctor); } variant(const variant& other) : _index(other._index) { xx_impl::init_visitor_2 ctor; apply(_storage, other._storage, _index, ctor); } variant(variant&& other) : _index(other._index) { xx_impl::init_visitor_2 ctor; apply(_storage, std::move(other._storage), _index, ctor); } private: template void perform_safe_copy(bool nothrow_movable, CA copy_action) { xx_impl::destroy_visitor dtor; if (nothrow_movable) { apply(_storage, _index, dtor); copy_action(); } else { struct backup_owner { size_t index; union_type* storage; constexpr bool can_restore() const noexcept { return index < sizeof...(T); } backup_owner() : index(sizeof...(T)), storage(new union_type) {} ~backup_owner() { if (storage != nullptr) { if (this->can_restore()) { xx_impl::destroy_visitor dtor; apply(*storage, index, dtor); } delete storage; } } } backup; xx_impl::init_visitor_2 ctor2; xx_impl::is_nothrow_movable_checker nothrow_movable_checker; try { if (xx_impl::apply(_storage, _index, nothrow_movable_checker)) xx_impl::apply(*backup.storage, std::move(_storage), _index, ctor2); else xx_impl::apply(*backup.storage, _storage, _index, ctor2); backup.index = _index; xx_impl::apply(_storage, _index, dtor); copy_action(); } catch (...) { if (backup.can_restore()) { if (xx_impl::apply(_storage, backup.index, nothrow_movable_checker)) xx_impl::apply(_storage, std::move(*backup.storage), backup.index, ctor2); else xx_impl::apply(_storage, *backup.storage, backup.index, ctor2); } throw; } } } public: template variant& operator=(U&& other) noexcept(xx_impl::is_generic_nothrow_assignable()) { typedef xx_impl::get_index index_tmpl; static_assert(index_tmpl::found, "Assigning to variant from unexpected type."); static_assert(!index_tmpl::ambiguous, "Assigning to variant with ambiguous conversion."); static const size_t index_of_U = index_tmpl::index; if (_index == index_of_U) { xx_impl::assign_visitor_1 copier (std::forward(other)); xx_impl::static_applier()(_storage, copier); } else { xx_impl::init_visitor_1 ctor (std::forward(other)); xx_impl::static_applier static_applier; this->perform_safe_copy(xx_impl::is_generic_nothrow_assignable(), [&, this] { static_applier(_storage, ctor); } ); _index = index_of_U; } return *this; } template typename std::enable_if, xx_impl::is_same, T...>::is_exact, variant>::type& operator=(variant& other) { xx_impl::is_same_visitor is_same; xx_impl::is_one_of_visitor has_same; auto same_type_pair = xx_impl::apply2(_storage, _index, other._storage, other._index, is_same); bool has_same_type = xx_impl::apply(other._storage, other._index, has_same); if (same_type_pair.first || (!has_same_type && same_type_pair.second)) { xx_impl::assign_visitor_2 assigner; xx_impl::apply2(_storage, _index, other._storage, other._index, assigner); } else { xx_impl::assign_to_visitor> assigner (*this); xx_impl::apply(other._storage, other._index, assigner); } return *this; } template variant& operator=(variant&& other) { xx_impl::is_same_visitor is_same; xx_impl::is_one_of_visitor has_same; auto same_type_pair = xx_impl::apply2(_storage, _index, other._storage, other._storage, is_same); bool has_same_type = xx_impl::apply(other._storage, other._index, has_same); if (same_type_pair.first || (!has_same_type && same_type_pair.second)) { xx_impl::assign_visitor_2 assigner; xx_impl::apply2(_storage, _index, std::move(other._storage), other._index, assigner); } else { xx_impl::assign_to_visitor> assigner (*this); xx_impl::apply(std::move(other._storage), other._index, assigner); } return *this; } variant& operator=(const variant& other) { if (_index == other._index) { xx_impl::assign_visitor_2 assigner; xx_impl::apply(_storage, other._storage, _index, assigner); } else { xx_impl::is_nothrow_copyable_checker cc; bool is_nothrow_copyable = xx_impl::apply(other._storage, other._index, cc); this->perform_safe_copy(is_nothrow_copyable, [=, &other] { xx_impl::init_visitor_2 ctor; xx_impl::apply(_storage, other._storage, other._index, ctor); } ); _index = other._index; } return *this; } variant& operator=(variant& other) { const auto& other_const = other; return (*this = other_const); } variant& operator=(variant&& other) { if (_index == other._index) { xx_impl::assign_visitor_2 assigner; apply(_storage, std::move(other._storage), _index, assigner); } else { xx_impl::is_nothrow_movable_checker mc; bool is_nothrow_movable = xx_impl::apply(other._storage, other._index, mc); this->perform_safe_copy(is_nothrow_movable, [=, &other] { xx_impl::init_visitor_2 ctor; xx_impl::apply(_storage, std::move(other._storage), other._index, ctor); } ); _index = other._index; } return *this; } void swap(variant& other) { if (_index == other._index) { xx_impl::swap_visitor_2 swapper; xx_impl::apply(_storage, other._storage, _index, swapper); } else { auto tmp = std::move(other); other = std::move(*this); *this = std::move(tmp); } } template bool operator==(const U& other) const { typedef xx_impl::get_index index_tmpl; static_assert(index_tmpl::found, "Equating variant to unexpected type."); static_assert(!index_tmpl::ambiguous, "Equating variant with ambiguous conversion."); static const size_t index_of_U = index_tmpl::index; if (index_of_U != _index) return false; xx_impl::equals_visitor_1 eq (other); return xx_impl::static_applier()(_storage, eq); } template bool operator<(const U& other) const { typedef xx_impl::get_index index_tmpl; static_assert(index_tmpl::found, "Comparing variant with unexpected type."); static_assert(!index_tmpl::ambiguous, "Comparing variant with ambiguous conversion."); static const size_t index_of_U = index_tmpl::index; if (index_of_U != _index) return _index < index_of_U; xx_impl::less_than_visitor_1 lt (other); return xx_impl::static_applier()(_storage, lt); } template bool operator>(const U& other) const { typedef xx_impl::get_index index_tmpl; static_assert(index_tmpl::found, "Comparing variant with unexpected type."); static_assert(!index_tmpl::ambiguous, "Comparing variant with ambiguous conversion."); static const size_t index_of_U = index_tmpl::index; if (index_of_U != _index) return _index > index_of_U; xx_impl::greater_than_visitor_1 gt (other); return xx_impl::static_applier()(_storage, gt); } bool operator==(const variant& other) const { if (_index != other._index) return false; xx_impl::equals_visitor_2 eq; return xx_impl::apply(_storage, other._storage, _index, eq); } bool operator<(const variant& other) const { if (_index != other._index) return _index < other._index; xx_impl::less_than_visitor_2 lt; return xx_impl::apply(_storage, other._storage, _index, lt); } template bool operator!=(const U& a) const { return !(*this == a); } template bool operator>=(const U& a) const { return !(*this < a); } template bool operator<=(const U& a) const { return !(*this > a); } #if !defined(BOOST_NO_TYPEID) /** .. function:: const std::type_info& type() const noexcept Returns the typeid of the currently active member. This function will not be available if you define BOOST_NO_TYPEID before including this header. */ const std::type_info& type() const noexcept { xx_impl::typeid_visitor tv; return xx_impl::apply(_storage, _index, tv); } #endif /** .. function:: bool is_type() const noexcept Check if the variant is currently containing the **exact type** *U*. */ template bool is_type() const noexcept { typedef xx_impl::get_index index_impl; static_assert(index_impl::is_exact, "Checking from unexpected type."); return index_impl::index == _index; } template friend typename SV::result_type apply_visitor(SV& visitor, V&& variant); template friend typename SV::result_type apply_visitor(SV& visitor, V1&& variant1, V2&& variant2); template friend typename SV::result_type apply_visitor(SV&& visitor, V&& variant); template friend typename SV::result_type apply_visitor(SV&& visitor, V1&& variant1, V2&& variant2); template friend U* get(variant* v) noexcept; template friend const U* get(const variant* v) noexcept; template friend typename xx_impl::common_result_type::type case_of(V&& variant, F&&... functions); template friend class variant; }; /** .. function:: VisitorType::result_type utils::apply_visitor(VisitorType visitor, const utils::variant& var) VisitorType::result_type utils::apply_visitor(VisitorType visitor, utils::variant& var) Perform an operation on the variant. The *visitor* must be a function object that accepts every possible type of *T*, and has the same return type for all overloads, which must be reported as ``VisitorType::result_type``. You could use the :type:`~utils::static_visitor` to ensure this. */ template typename SV::result_type apply_visitor(SV& visitor, V&& variant) { return xx_impl::apply(forward_like(variant._storage), variant._index, visitor); } template typename SV::result_type apply_visitor(SV&& visitor, V&& variant) { return xx_impl::apply(forward_like(variant._storage), variant._index, visitor); } /** .. function:: VisitorType::result_type utils::apply_visitor(VisitorType visitor, const utils::variant& var1, const utils::variant& var2) VisitorType::result_type utils::apply_visitor(VisitorType visitor, utils::variant& var1, const utils::variant& var2) VisitorType::result_type utils::apply_visitor(VisitorType visitor, const utils::variant& var1, utils::variant& var2) VisitorType::result_type utils::apply_visitor(VisitorType visitor, utils::variant& var1, utils::variant& var2) Perform double visitation on a pair of variants. */ template typename SV::result_type apply_visitor(SV& visitor, V1&& variant1, V2&& variant2) { return xx_impl::apply2(variant1._storage, variant1._index, variant2._storage, variant2._index, visitor); } template typename SV::result_type apply_visitor(SV&& visitor, V1&& variant1, V2&& variant2) { return xx_impl::apply2(variant1._storage, variant1._index, variant2._storage, variant2._index, visitor); } /** .. function:: auto utils::apply_visitor(VisitorType visitor) Return a function object that will perform single or double visitation when called. In Boost this is known as *delayed visitation*. */ template xx_impl::delayed_visitor apply_visitor(SV& visitor) { return xx_impl::delayed_visitor(visitor); } /** .. function:: auto utils::case_of(const utils::variant& var, F&&... functions) auto utils::case_of(utils::variant& var, F&&... functions) Apply one of the functions which has compatible argument type to the variant, and return that result. */ template typename xx_impl::common_result_type::type case_of(V&& variant, F&&... functions) { return xx_impl::apply_funcs_run(forward_like(variant._storage), variant._index, std::forward(functions)...); } /** .. function:: U* utils::get(utils::variant* var_ptr) noexcept const U* utils::get(const utils::variant* var_ptr) noexcept Obtain a pointer to *U* if the variant's active member is really of type *U*. Return ``nullptr`` if not. */ template U* get(variant* v) noexcept { typedef xx_impl::get_index index_impl; static_assert(index_impl::is_exact, "Getting from unexpected type."); if (index_impl::index == v->_index) { xx_impl::getter_visitor getter; return xx_impl::static_applier()(v->_storage, getter); } else return nullptr; } template const U* get(const variant* v) noexcept { typedef xx_impl::get_index index_impl; static_assert(index_impl::is_exact, "Getting from unexpected type."); if (index_impl::index == v->_index) { xx_impl::getter_visitor getter; return xx_impl::static_applier()(v->_storage, getter); } else return nullptr; } /** .. type:: class utils::bad_get : public std::exception This exception is thrown when trying to :func:`~utils::get` a reference from a variant that does not have the required type. */ class bad_get : public std::exception { public: virtual const char* what() const noexcept { return "bad_get"; } }; /** .. function:: U& utils::get(utils::variant& var) const U& utils::get(const utils::variant& var) Obtain a reference to *U* if the variant's active member is really of type *U*. Throws a :type:`~utils::bad_get` exception if not. */ template U& get(variant& v) { auto res = get(&v); if (res == nullptr) throw bad_get(); return *res; } template const U& get(const variant& v) { auto res = get(&v); if (res == nullptr) throw bad_get(); return *res; } template bool operator==(const U& a, const variant& v) { return v == a; } template bool operator<(const U& a, const variant& v) { return v > a; } template bool operator>(const U& a, const variant& v) { return v < a; } template bool operator!=(const U& a, const variant& v) { return !(v == a); } template bool operator>=(const U& a, const variant& v) { return !(v > a); } template bool operator<=(const U& a, const variant& v) { return !(v < a); } template std::ostream& operator<<(std::ostream& stream, const variant& v) { xx_impl::ostream_visitor visitor (stream); return apply_visitor(visitor, v); } } namespace std { template void swap(utils::variant& a, utils::variant& b) { a.swap(b); } } #endif