/* MIT License Copyright (c) 2021 Jean-Baptiste Vallon Hoarau Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SWL_CPP_LIBRARY_VARIANT_HPP #define SWL_CPP_LIBRARY_VARIANT_HPP #include #include #include // swap #include // used for index_type #include // a user-provided header for tweaks // possible macros defined : // SWL_VARIANT_NO_CONSTEXPR_EMPLACE // SWL_VARIANT_NO_STD_HASH #if __has_include("swl_variant_knobs.hpp") #include "swl_variant_knobs.hpp" #endif #ifndef SWL_VARIANT_NO_CONSTEXPR_EMPLACE #include #else #include #endif #ifndef SWL_VARIANT_NO_STD_HASH #include #endif #ifndef __has_builtin #define __has_builtin(x) 0 #endif #define SWL_FWD(x) static_cast(x) #define SWL_MOV(x) static_cast< std::remove_reference_t&& >(x) #ifdef SWL_VARIANT_DEBUG #include #define DebugAssert(X) if (not (X)) std::cout << "Variant : assertion failed : [" << #X << "] at line : " << __LINE__ << "\n"; #undef SWL_VARIANT_DEBUG #else #define DebugAssert(X) #endif namespace swl { class bad_variant_access final : public std::exception { const char* message = ""; // llvm test requires a well formed what() on default init public : bad_variant_access(const char* str) noexcept : message{str} {} bad_variant_access() noexcept = default; bad_variant_access(const bad_variant_access&) noexcept = default; bad_variant_access& operator= (const bad_variant_access&) noexcept = default; const char* what() const noexcept override { return message; } }; namespace vimpl { struct variant_tag{}; struct emplacer_tag{}; } template struct in_place_type_t : private vimpl::emplacer_tag {}; template struct in_place_index_t : private vimpl::emplacer_tag {}; template inline static constexpr in_place_index_t in_place_index; template inline static constexpr in_place_type_t in_place_type; namespace vimpl { #include "variant_detail.hpp" #include "variant_visit.hpp" struct variant_npos_t { template constexpr bool operator==(T idx) const noexcept { return idx == std::numeric_limits::max(); } }; } template inline constexpr bool is_variant = std::is_base_of_v>; inline static constexpr vimpl::variant_npos_t variant_npos; template class variant; // ill-formed variant, an empty specialization prevents some really bad errors messages on gcc template requires ( (std::is_array_v || ...) || (std::is_reference_v || ...) || (std::is_void_v || ...) || sizeof...(Ts) == 0 ) class variant { static_assert( sizeof...(Ts) > 0, "A variant cannot be empty."); static_assert( not (std::is_reference_v || ...), "A variant cannot contain references, consider using reference wrappers instead." ); static_assert( not (std::is_void_v || ...), "A variant cannot contains void." ); static_assert( not (std::is_array_v || ...), "A variant cannot contain a raw array type, consider using std::array instead." ); }; template class variant : private vimpl::variant_tag { using storage_t = vimpl::union_node, vimpl::dummy_type>; static constexpr bool is_trivial = std::is_trivial_v; static constexpr bool has_copy_ctor = std::is_copy_constructible_v; static constexpr bool trivial_copy_ctor = is_trivial || std::is_trivially_copy_constructible_v; static constexpr bool has_copy_assign = std::is_copy_constructible_v; static constexpr bool trivial_copy_assign = is_trivial || std::is_trivially_copy_assignable_v; static constexpr bool has_move_ctor = std::is_move_constructible_v; static constexpr bool trivial_move_ctor = is_trivial || std::is_trivially_move_constructible_v; static constexpr bool has_move_assign = std::is_move_assignable_v; static constexpr bool trivial_move_assign = is_trivial || std::is_trivially_move_assignable_v; static constexpr bool trivial_dtor = std::is_trivially_destructible_v; public : template using alternative = std::remove_reference_t< decltype( std::declval().template get() ) >; static constexpr bool can_be_valueless = not is_trivial; static constexpr unsigned size = sizeof...(Ts); using index_type = vimpl::smallest_suitable_integer_type; static constexpr index_type npos = -1; template static constexpr int index_of = vimpl::find_first_true( {std::is_same_v...} ); // ============================================= constructors (20.7.3.2) // default constructor constexpr variant() noexcept (std::is_nothrow_default_constructible_v>) requires std::is_default_constructible_v> : storage{ in_place_index<0> }, current{0} {} // copy constructor (trivial) constexpr variant(const variant&) requires trivial_copy_ctor = default; // note : both the copy and move constructor cannot be meaningfully constexpr without std::construct_at // copy constructor constexpr variant(const variant& o) requires (has_copy_ctor and not trivial_copy_ctor) : storage{ vimpl::dummy_type{} } { construct_from(o); } // move constructor (trivial) constexpr variant(variant&&) requires trivial_move_ctor = default; // move constructor constexpr variant(variant&& o) noexcept ((std::is_nothrow_move_constructible_v && ...)) requires (has_move_ctor and not trivial_move_ctor) : storage{ vimpl::dummy_type{} } { construct_from(static_cast(o)); } // generic constructor template , class D = std::decay_t> constexpr variant(T&& t) noexcept ( std::is_nothrow_constructible_v ) requires ( not std::is_same_v and not std::is_base_of_v ) : variant{ in_place_index< index_of >, static_cast(t) } {} // construct at index template requires (Index < size && std::is_constructible_v, Args&&...>) explicit constexpr variant(in_place_index_t tag, Args&&... args) : storage{tag, static_cast(args)...}, current(Index) {} // construct a given type template requires (vimpl::appears_exactly_once && std::is_constructible_v) explicit constexpr variant(in_place_type_t, Args&&... args) : variant{ in_place_index< index_of >, static_cast(args)... } {} // initializer-list constructors template requires ( (Index < size) and std::is_constructible_v< alternative, std::initializer_list&, Args&&... > ) explicit constexpr variant (in_place_index_t tag, std::initializer_list list, Args&&... args) : storage{ tag, list, SWL_FWD(args)... }, current{Index} {} template requires ( vimpl::appears_exactly_once && std::is_constructible_v< T, std::initializer_list&, Args&&... > ) explicit constexpr variant (in_place_type_t, std::initializer_list list, Args&&... args) : storage{ in_place_index>, list, SWL_FWD(args)... }, current{index_of} {} // ================================ destructors (20.7.3.3) constexpr ~variant() requires trivial_dtor = default; constexpr ~variant() requires (not trivial_dtor) { reset(); } // ================================ assignment (20.7.3.4) // copy assignment (trivial) constexpr variant& operator=(const variant& o) requires trivial_copy_assign && trivial_copy_ctor = default; // copy assignment constexpr variant& operator=(const variant& rhs) requires (has_copy_assign and not(trivial_copy_assign && trivial_copy_ctor)) { assign_from(rhs, [this] (const auto& elem, auto index_cst) { if (index() == index_cst) unsafe_get() = elem; else{ using type = alternative; if constexpr (std::is_nothrow_copy_constructible_v or not std::is_nothrow_move_constructible_v) this->emplace(elem); else{ alternative tmp = elem; this->emplace( SWL_MOV(tmp) ); } } }); return *this; } // move assignment (trivial) constexpr variant& operator=(variant&& o) requires (trivial_move_assign and trivial_move_ctor and trivial_dtor) = default; // move assignment constexpr variant& operator=(variant&& o) noexcept ((std::is_nothrow_move_constructible_v && ...) && (std::is_nothrow_move_assignable_v && ...)) requires (has_move_assign && has_move_ctor and not(trivial_move_assign and trivial_move_ctor and trivial_dtor)) { this->assign_from( SWL_FWD(o), [this] (auto&& elem, auto index_cst) { if (index() == index_cst) this->unsafe_get() = SWL_MOV(elem); else this->emplace( SWL_MOV(elem) ); }); return *this; } // generic assignment template requires vimpl::has_non_ambiguous_match constexpr variant& operator=(T&& t) noexcept( std::is_nothrow_assignable_v, T&&> && std::is_nothrow_constructible_v, T&&> ) { using related_type = vimpl::best_overload_match; constexpr auto new_index = index_of; if (this->current == new_index) this->unsafe_get() = SWL_FWD(t); else { constexpr bool do_simple_emplace = std::is_nothrow_constructible_v or not std::is_nothrow_move_constructible_v; if constexpr ( do_simple_emplace ) this->emplace( SWL_FWD(t) ); else { related_type tmp = t; this->emplace( SWL_MOV(tmp) ); } } return *this; } // ================================== modifiers (20.7.3.5) template requires (std::is_constructible_v && vimpl::appears_exactly_once) constexpr T& emplace(Args&&... args){ return emplace>(static_cast(args)...); } template requires (Idx < size and std::is_constructible_v, Args&&...> ) constexpr auto& emplace(Args&&... args){ return emplace_impl(SWL_FWD(args)...); } // emplace with initializer-lists template requires ( Idx < size && std::is_constructible_v, std::initializer_list&, Args&&...> ) constexpr auto& emplace(std::initializer_list list, Args&&... args){ return emplace_impl(list, SWL_FWD(args)...); } template requires ( std::is_constructible_v&, Args&&...> && vimpl::appears_exactly_once ) constexpr T& emplace(std::initializer_list list, Args&&... args){ return emplace_impl>( list, SWL_FWD(args)... ); } // ==================================== value status (20.7.3.6) constexpr bool valueless_by_exception() const noexcept { if constexpr ( can_be_valueless ) return current == npos; else return false; } constexpr index_type index() const noexcept { return current; } // =================================== swap (20.7.3.7) constexpr void swap(variant& o) noexcept ( (std::is_nothrow_move_constructible_v && ...) && (vimpl::swap_trait::template nothrow && ...) ) requires (has_move_ctor && (vimpl::swap_trait::template able && ...)) { if constexpr (can_be_valueless){ // if one is valueless, move the element form the non-empty variant, // reset it, and set it to valueless constexpr auto impl_one_valueless = [] (auto&& full, auto& empty) { vimpl::visit_with_index( SWL_FWD(full), vimpl::emplace_no_dtor_from_elem{empty} ); full.reset_no_check(); full.current = npos; }; switch( static_cast(this->index() == npos) + static_cast(o.index() == npos) * 2 ) { case 0 : break; case 1 : // "this" is valueless impl_one_valueless( SWL_MOV(o), *this ); return; case 2 : // "other" is valueless impl_one_valueless( SWL_MOV(*this), o ); return; case 3 : // both are valueless, do nothing return; } } DebugAssert( not (valueless_by_exception() && o.valueless_by_exception()) ); vimpl::visit_with_index( o, [&o, this] (auto&& elem, auto index_) { if (this->index() == index_){ using std::swap; swap( this->unsafe_get(), elem ); return; } using idx_t = decltype(index_); vimpl::visit_with_index(*this, [this, &o, &elem] (auto&& this_elem, auto this_index) { auto tmp { SWL_MOV(this_elem) }; // destruct the element vimpl::destruct>(this_elem); // ok, we just destroyed the element in this, don't call the dtor again this->emplace_no_dtor( SWL_MOV(elem) ); // we could refactor this vimpl::destruct>(elem); o.template emplace_no_dtor< (unsigned)(this_index) >( SWL_MOV(tmp) ); }); }); } // +================================== methods for internal use // these methods performs no errors checking at all template constexpr auto& unsafe_get() & noexcept { static_assert(Idx < size); DebugAssert(current == Idx); return storage.template get(); } template constexpr auto&& unsafe_get() && noexcept { static_assert(Idx < size); DebugAssert(current == Idx); return SWL_MOV( storage.template get() ); } template constexpr const auto& unsafe_get() const & noexcept { static_assert(Idx < size); DebugAssert(current == Idx); return const_cast(*this).unsafe_get(); } template constexpr const auto&& unsafe_get() const && noexcept { static_assert(Idx < size); DebugAssert(current == Idx); return SWL_MOV(unsafe_get()); } private : // assign from another variant template constexpr void assign_from(Other&& o, Fn&& fn){ if constexpr (can_be_valueless){ if (o.index() == npos){ if (current != npos){ reset_no_check(); current = npos; } return; } } DebugAssert(not o.valueless_by_exception()); vimpl::visit_with_index( SWL_FWD(o), SWL_FWD(fn) ); } template constexpr auto& emplace_impl(Args&&... args) { reset(); emplace_no_dtor( SWL_FWD(args)... ); return unsafe_get(); } // can be used directly only when the variant is in a known empty state template constexpr void emplace_no_dtor(Args&&... args) { using T = alternative; if constexpr ( not std::is_nothrow_constructible_v ) { if constexpr ( std::is_nothrow_move_constructible_v ) do_emplace_no_dtor( T{SWL_FWD(args)...} ); else if constexpr ( std::is_nothrow_copy_constructible_v ) { T tmp {SWL_FWD(args)...}; do_emplace_no_dtor( tmp ); } else { static_assert( can_be_valueless && (Idx == Idx), "Internal error : the possibly valueless branch of emplace was taken despite |can_be_valueless| being false"); current = npos; do_emplace_no_dtor( SWL_FWD(args)... ); } } else do_emplace_no_dtor( SWL_FWD(args)... ); } template constexpr void do_emplace_no_dtor(Args&&... args) { auto* ptr = vimpl::addressof( unsafe_get() ); #ifdef SWL_VARIANT_NO_CONSTEXPR_EMPLACE using T = alternative; new ((void*)(ptr)) T ( SWL_FWD(args)... ); #else std::construct_at( ptr, SWL_FWD(args)... ); #endif current = static_cast(Idx); } // destroy the current elem IFF not valueless constexpr void reset() { if constexpr (can_be_valueless) if (valueless_by_exception()) return; reset_no_check(); } // destroy the current element without checking for valueless constexpr void reset_no_check(){ DebugAssert( index() < size ); if constexpr ( not trivial_dtor ){ vimpl::visit_with_index( *this, [] (auto& elem, auto index_) { vimpl::destruct>(elem); }); } } // construct this from another variant, for constructors only template constexpr void construct_from(Other&& o){ if constexpr (can_be_valueless) if (o.valueless_by_exception()){ current = npos; return; } vimpl::visit_with_index( SWL_FWD(o), vimpl::emplace_no_dtor_from_elem{*this} ); } template friend struct vimpl::emplace_no_dtor_from_elem; storage_t storage; index_type current; }; // ================================= value access (20.7.5) template constexpr bool holds_alternative(const variant& v) noexcept { static_assert( (std::is_same_v || ...), "Requested type is not contained in the variant" ); constexpr auto Index = variant::template index_of; return v.index() == Index; } // ========= get by index template constexpr auto& get (variant& v){ static_assert( Idx < sizeof...(Ts), "Index exceeds the variant size. "); if (v.index() != Idx) throw bad_variant_access{"swl::variant : Bad variant access in get."}; return (v.template unsafe_get()); } template constexpr const auto& get (const variant& v){ return swl::get(const_cast&>(v)); } template constexpr auto&& get (variant&& v){ return SWL_MOV( swl::get(v) ); } template constexpr const auto&& get (const variant&& v){ return SWL_MOV( swl::get(v) ); } // ========= get by type template constexpr T& get (variant& v){ return swl::get< variant::template index_of >(v); } template constexpr const T& get (const variant& v){ return swl::get< variant::template index_of >(v); } template constexpr T&& get (variant&& v){ return swl::get< variant::template index_of >( SWL_FWD(v) ); } template constexpr const T&& get (const variant&& v){ return swl::get< variant::template index_of >( SWL_FWD(v) ); } // ===== get_if by index template constexpr const auto* get_if(const variant* v) noexcept { using rtype = typename variant::template alternative*; if (v == nullptr || v->index() != Idx) return rtype{nullptr}; else return vimpl::addressof( v->template unsafe_get() ); } template constexpr auto* get_if(variant* v) noexcept { using rtype = typename variant::template alternative; return const_cast( swl::get_if( static_cast*>(v) ) ); } // ====== get_if by type template constexpr T* get_if(variant* v) noexcept { static_assert( (std::is_same_v || ...), "Requested type is not contained in the variant" ); return swl::get_if< variant::template index_of >(v); } template constexpr const T* get_if(const variant* v) noexcept { static_assert( (std::is_same_v || ...), "Requested type is not contained in the variant" ); return swl::get_if< variant::template index_of >(v); } // =============================== visitation (20.7.7) template constexpr decltype(auto) visit(Fn&& fn, Vs&&... vs){ if constexpr ( (std::decay_t::can_be_valueless || ...) ) if ( (vs.valueless_by_exception() || ...) ) throw bad_variant_access{"swl::variant : Bad variant access in visit."}; if constexpr (sizeof...(Vs) == 1) return vimpl::visit( SWL_FWD(fn), SWL_FWD(vs)...); else return vimpl::multi_visit( SWL_FWD(fn), SWL_FWD(vs)...); } template constexpr decltype(auto) visit(Fn&& fn){ return SWL_FWD(fn)(); } template requires (is_variant && ...) constexpr R visit(Fn&& fn, Vs&&... vars){ return static_cast( swl::visit( SWL_FWD(fn), SWL_FWD(vars)...) ); } // ============================== relational operators (20.7.6) template requires ( vimpl::has_eq_comp && ... ) constexpr bool operator==(const variant& v1, const variant& v2){ if (v1.index() != v2.index()) return false; if constexpr (variant::can_be_valueless) if (v1.valueless_by_exception()) return true; return vimpl::visit_with_index( v2, [&v1] (auto& elem, auto index) -> bool { return (v1.template unsafe_get() == elem); }); } template constexpr bool operator!=(const variant& v1, const variant& v2) requires requires { v1 == v2; } { return not(v1 == v2); } template requires ( vimpl::has_lesser_comp && ... ) constexpr bool operator<(const variant& v1, const variant& v2){ if constexpr (variant::can_be_valueless){ if (v2.valueless_by_exception()) return false; if (v1.valueless_by_exception()) return true; } if ( v1.index() == v2.index() ){ return vimpl::visit_with_index( v1, [&v2] (auto& elem, auto index) -> bool { return (elem < v2.template unsafe_get()); } ); } else return (v1.index() < v2.index()); } template constexpr bool operator>(const variant& v1, const variant& v2) requires requires { v2 < v1; } { return v2 < v1; } template requires ( vimpl::has_less_or_eq_comp && ... ) constexpr bool operator<=(const variant& v1, const variant& v2){ if constexpr (variant::can_be_valueless){ if (v1.valueless_by_exception()) return true; if (v2.valueless_by_exception()) return false; } if ( v1.index() == v2.index() ){ return vimpl::visit_with_index( v1, [&v2] (auto& elem, auto index) -> bool { return (elem <= v2.template unsafe_get()); }); } else return (v1.index() < v2.index()); } template constexpr bool operator>=(const variant& v1, const variant& v2) requires requires { v2 <= v1; } { return v2 <= v1; } // ===================================== monostate (20.7.8, 20.7.9) struct monostate{}; constexpr bool operator==(monostate, monostate) noexcept { return true; } constexpr bool operator> (monostate, monostate) noexcept { return false; } constexpr bool operator< (monostate, monostate) noexcept { return false; } constexpr bool operator<=(monostate, monostate) noexcept { return true; } constexpr bool operator>=(monostate, monostate) noexcept { return true; } // ===================================== specialized algorithms (20.7.10) template constexpr void swap(variant& a, variant& b) noexcept (noexcept(a.swap(b))) requires requires { a.swap(b); } { a.swap(b); } // ===================================== helper classes (20.7.4) template requires is_variant inline constexpr std::size_t variant_size_v = std::decay_t::size; // not sure why anyone would need this, i'm adding it anyway template requires is_variant struct variant_size : std::integral_constant> {}; namespace vimpl { // ugh, we have to take care of volatile here template struct var_alt_impl { template using type = std::remove_reference_t().template unsafe_get() )>; }; template <> struct var_alt_impl { template using type = volatile typename var_alt_impl::template type>; }; } template requires (Idx < variant_size_v) using variant_alternative_t = typename vimpl::var_alt_impl< std::is_volatile_v >::template type; template requires is_variant struct variant_alternative { using type = variant_alternative_t; }; // ===================================== extensions (unsafe_get) template requires is_variant constexpr auto&& unsafe_get(Var&& var) noexcept { static_assert( Idx < std::decay_t::size, "Index exceeds the variant size." ); return SWL_FWD(var).template unsafe_get(); } template requires is_variant constexpr auto&& unsafe_get(Var&& var) noexcept { return swl::unsafe_get< std::decay_t::template index_of >( SWL_FWD(var) ); } } // SWL // ====================================== hash support (20.7.12) #ifndef SWL_VARIANT_NO_STD_HASH namespace std { template requires (swl::vimpl::has_std_hash && ...) struct hash> { std::size_t operator()(const swl::variant& v) const { if constexpr ( swl::variant::can_be_valueless ) if (v.valueless_by_exception()) return -1; return swl::vimpl::visit_with_index( v, [] (auto& elem, auto index_) { using type = std::remove_cvref_t; return std::hash{}(elem) + index_; }); } }; template <> struct hash { constexpr std::size_t operator()(swl::monostate) const noexcept { return -1; } }; } #else #undef SWL_VARIANT_NO_STD_HASH #endif // std-hash #undef DebugAssert #undef SWL_FWD #undef SWL_MOV #ifdef SWL_VARIANT_NO_CONSTEXPR_EMPLACE #undef SWL_VARIANT_NO_CONSTEXPR_EMPLACE #endif #endif // eof