/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Implements the class template variant represents a type-safe union. An // instance of variant at any given time either holds a value of one of its // alternative types, or it holds no value. // // As with unions, if a variant holds a value of some object type T, the object // representation of T is allocated directly within the object representation of // the variant itself. // // Variant is not allowed to allocate additional (dynamic) memory. // // A variant is not permitted to hold references, arrays, or the type void. // Empty variants are also ill-formed (variant can be used instead). // // A variant is permitted to hold the same type more than once, and to hold // differently cv-qualified versions of the same type. As with unions, the // default-initialized variant holds a value of its first alternative, unless // that alternative is not default-constructible (in which case default // constructor won't compile: the helper class monostate can be used to make // such variants default-constructible) // // Given defect 2901, the eastl::variant implementation does not provide the // specified allocator-aware functions. This will be re-evaluated when the LWG // addresses this issue in future standardization updates. // LWG Defect 2901: https://cplusplus.github.io/LWG/issue2901 // // Allocator-extended constructors // template variant(allocator_arg_t, const Alloc&); // template variant(allocator_arg_t, const Alloc&, const variant&); // template variant(allocator_arg_t, const Alloc&, variant&&); // template variant(allocator_arg_t, const Alloc&, T&&); // template variant(allocator_arg_t, const Alloc&, in_place_type_t, Args&&...); // template variant(allocator_arg_t, const Alloc&, in_place_type_t, initializer_list, Args&&...); // template variant(allocator_arg_t, const Alloc&, in_place_index_t, Args&&...); // template variant(allocator_arg_t, const Alloc&, in_place_index_t, initializer_list, Args&&...); // // 20.7.12, allocator-related traits // template struct uses_allocator; // template struct uses_allocator, Alloc>; // // eastl::variant doesn't support: // * recursive variant support // * strong exception guarantees as specified (we punted on the assignment problem). // if an exception is thrown during assignment its undefined behaviour in our implementation. // // Reference: // * http://en.cppreference.com/w/cpp/utility/variant // * https://thenewcpp.wordpress.com/2012/02/15/variadic-templates-part-3-or-how-i-wrote-a-variant-class/ /////////////////////////////////////////////////////////////////////////// #ifndef EASTL_VARIANT_H #define EASTL_VARIANT_H #include #include #include #include #include #include #include #include #include #if defined(EA_PRAGMA_ONCE_SUPPORTED) #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result. #endif #ifndef EA_COMPILER_CPP14_ENABLED static_assert(false, "eastl::variant requires a C++14 compatible compiler (at least) "); #endif EA_DISABLE_VC_WARNING(4625) // copy constructor was implicitly defined as deleted namespace eastl { namespace internal { /////////////////////////////////////////////////////////////////////////// // default_construct_if_supported // // Utility class to remove default constructor calls for types that // do not support default construction. // // We can remove these utilities when C++17 'constexpr if' is available. // template> struct default_construct_if_supported { static void call(T* pThis) { new (pThis) T(); } }; template struct default_construct_if_supported { static void call(T*) {} // intentionally blank }; /////////////////////////////////////////////////////////////////////////// // destroy_if_supported // // Utility class to remove default constructor calls for types that // do not support default construction. // // We can remove these utilities when C++17 'constexpr if' is available. // template> struct destroy_if_supported { static void call(T* pThis) { pThis->~T(); } }; template struct destroy_if_supported { static void call(T* pThis) {} // intentionally blank }; /////////////////////////////////////////////////////////////////////////// // copy_if_supported // // Utility class to remove copy constructor calls for types that // do not support copying. // // We can remove these utilities when C++17 'constexpr if' is available. // template> struct copy_if_supported { static void call(T* pThis, T* pOther) { new (pThis) T(*pOther); } }; template struct copy_if_supported { static void call(T* pThis, T* pOther) {} // intentionally blank }; /////////////////////////////////////////////////////////////////////////// // move_if_supported // // Utility class to remove move constructor calls for types that // do not support moves. // // We can remove these utilities when C++17 'constexpr if' is available. // template> struct move_if_supported { static void call(T* pThis, T* pOther) { new (pThis) T(eastl::move(*pOther)); } }; template struct move_if_supported { static void call(T* pThis, T* pOther) {} // intentionally blank }; } // namespace internal /////////////////////////////////////////////////////////////////////////// // 20.7.3, variant_npos // EASTL_CPP17_INLINE_VARIABLE EA_CONSTEXPR size_t variant_npos = size_t(-1); /////////////////////////////////////////////////////////////////////////// // 20.7.10, class bad_variant_access // #if EASTL_EXCEPTIONS_ENABLED struct bad_variant_access : public std::logic_error { bad_variant_access() : std::logic_error("eastl::bad_variant_access exception") {} virtual ~bad_variant_access() EA_NOEXCEPT {} }; #endif /////////////////////////////////////////////////////////////////////////// // TODO(rparolin): JUST COPY/PASTE THIS CODE // inline void CheckVariantCondition(bool b) { EA_UNUSED(b); #if EASTL_EXCEPTIONS_ENABLED if (!b) throw bad_variant_access(); #elif EASTL_ASSERT_ENABLED EASTL_ASSERT_MSG(b, "eastl::bad_variant_access assert"); #endif } /////////////////////////////////////////////////////////////////////////// // 20.7.7, class monostate // // Unit type intended for use as a well-behaved empty alternative in // variant. A variant of non-default-constructible types may list monostate // as its first alternative: this makes the variant itself default-contructible. // struct monostate {}; // 20.7.8, monostate relational operators EA_CONSTEXPR bool operator> (monostate, monostate) EA_NOEXCEPT { return false; } EA_CONSTEXPR bool operator< (monostate, monostate) EA_NOEXCEPT { return false; } EA_CONSTEXPR bool operator!=(monostate, monostate) EA_NOEXCEPT { return false; } EA_CONSTEXPR bool operator<=(monostate, monostate) EA_NOEXCEPT { return true; } EA_CONSTEXPR bool operator>=(monostate, monostate) EA_NOEXCEPT { return true; } EA_CONSTEXPR bool operator==(monostate, monostate) EA_NOEXCEPT { return true; } // 20.7.11, hash support template struct hash; template <> struct hash { size_t operator()(monostate) const { return static_cast(-0x42); } }; /////////////////////////////////////////////////////////////////////////// // variant_storage // // This is a utility class to simplify the implementation of a storage type // for a distriminted union. This utility handles the alignment, size // requirements, and data access required by the variant type. // template struct variant_storage; // variant_storage // // specialization for non-trivial types (must call constructors and destructors) // template struct variant_storage { enum class StorageOp { DEFAULT_CONSTRUCT, DESTROY, COPY, MOVE }; // handler function using storage_handler_ptr = void(*)(StorageOp, void*, void*); using aligned_storage_impl_t = aligned_union_t<16, Types...>; aligned_storage_impl_t mBuffer; storage_handler_ptr mpHandler = nullptr; template inline void DoOp(StorageOp op, VariantStorageT&& other) // bind to both rvalue and lvalues { if(mpHandler) DoOp(StorageOp::DESTROY); if (other.mpHandler) mpHandler = other.mpHandler; if(mpHandler) mpHandler(op, (void*)&mBuffer, (void*)&other.mBuffer); } inline void DoOp(StorageOp op) { if(mpHandler) mpHandler(op, &mBuffer, nullptr); } template static void DoOpImpl(StorageOp op, T* pThis, T* pOther) { switch (op) { case StorageOp::DEFAULT_CONSTRUCT: { internal::default_construct_if_supported::call(pThis); } break; case StorageOp::DESTROY: { internal::destroy_if_supported::call(pThis); } break; case StorageOp::COPY: { internal::copy_if_supported::call(pThis, pOther); } break; case StorageOp::MOVE: { internal::move_if_supported::call(pThis, pOther); } break; default: {} break; }; } public: variant_storage() { DoOp(StorageOp::DEFAULT_CONSTRUCT); } ~variant_storage() { DoOp(StorageOp::DESTROY); } variant_storage(const variant_storage& other) { DoOp(StorageOp::COPY, other); } variant_storage(variant_storage&& other) { DoOp(StorageOp::MOVE, other); } variant_storage& operator=(const variant_storage& other) { DoOp(StorageOp::COPY, other); return *this; } variant_storage& operator=(variant_storage&& other) { DoOp(StorageOp::MOVE, eastl::move(other)); return *this; } template void set_as(Args&&... args) { // NOTE(rparolin): If this assert fires there is an EASTL problem picking the size of the local buffer which // variant_storage used to store types. The size selected should be large enough to hold the largest type in // the user provided variant type-list. static_assert(sizeof(aligned_storage_impl_t) >= sizeof(T), "T is larger than local buffer size"); using RT = remove_reference_t; new (&mBuffer) RT(eastl::forward(args)...); mpHandler = (storage_handler_ptr)&DoOpImpl; } template void set_as(std::initializer_list il, Args&&... args) { // NOTE(rparolin): If this assert fires there is an EASTL problem picking the size of the local buffer which // variant_storage used to store types. The size selected should be large enough to hold the largest type in // the user provided variant type-list. static_assert(sizeof(aligned_storage_impl_t) >= sizeof(T), "T is larger than local buffer size"); using RT = remove_reference_t; new (&mBuffer) RT(il, eastl::forward(args)...); mpHandler = (storage_handler_ptr)&DoOpImpl; } template T get_as() { static_assert(eastl::is_pointer_v, "T must be a pointer type"); return reinterpret_cast(&mBuffer); } template const T get_as() const { static_assert(eastl::is_pointer_v, "T must be a pointer type"); return reinterpret_cast(reinterpret_cast(&mBuffer)); } void destroy() { DoOp(StorageOp::DESTROY); } }; // variant_storage // // specialization for trivial types // template struct variant_storage { using aligned_storage_impl_t = aligned_union_t<16, Types...>; aligned_storage_impl_t mBuffer; public: // NOTE(rparolin): Since this is the specialization for trivial types can we potentially remove all the // defaulted special constructors. Consider removing this. // // variant_storage() = default; // ~variant_storage() = default; // variant_storage(const variant_storage& other) = default; // variant_storage(variant_storage&& other) = default; // variant_storage& operator=(const variant_storage& other) = default; // variant_storage& operator=(variant_storage&& other) = default; template void set_as(Args&&... args) { // NOTE(rparolin): If this assert fires there is an EASTL problem picking the size of the local buffer which // variant_storage used to store types. The size selected should be large enough to hold the largest type in // the user provided variant type-list. static_assert(sizeof(aligned_storage_impl_t) >= sizeof(T), "T is larger than local buffer size"); new (&mBuffer) remove_reference_t(eastl::forward(args)...); // mpHandler = ...; // member does not exist in this template specialization } template void set_as(std::initializer_list il, Args&&... args) { // NOTE(rparolin): If this assert fires there is an EASTL problem picking the size of the local buffer which // variant_storage used to store types. The size selected should be large enough to hold the largest type in // the user provided variant type-list. static_assert(sizeof(aligned_storage_impl_t) >= sizeof(T), "T is larger than local buffer size"); new (&mBuffer) remove_reference_t(il, eastl::forward(args)...); // mpHandler = ...; // member does not exist in this template specialization } template T get_as() { static_assert(eastl::is_pointer_v, "T must be a pointer type"); return reinterpret_cast(&mBuffer); } template const T get_as() const { static_assert(eastl::is_pointer_v, "T must be a pointer type"); return reinterpret_cast(reinterpret_cast(&mBuffer)); } void destroy() {} }; /////////////////////////////////////////////////////////////////////////// // 20.7.2, forward-declaration for types that depend on the variant // template class variant; /////////////////////////////////////////////////////////////////////////// // 20.7.3, variant_size, variant_size_v helper classes // template struct variant_size; template struct variant_size : integral_constant::value> {}; template struct variant_size : integral_constant::value> {}; template struct variant_size : integral_constant::value> {}; template struct variant_size> : integral_constant {}; // variant_size_v template alias template EA_CONSTEXPR size_t variant_size_v = variant_size::value; /////////////////////////////////////////////////////////////////////////// // variant_alternative_helper // // This helper does the heavy lifting of traversing the variadic type list // and retrieving the type at the user provided index. // template struct variant_alternative_helper; template struct variant_alternative_helper { typedef typename variant_alternative_helper::type type; }; template struct variant_alternative_helper<0, Head, Tail...> { typedef Head type; }; /////////////////////////////////////////////////////////////////////////// // 20.7.4, variant_alternative // template struct variant_alternative; template struct variant_alternative> : variant_alternative_helper {}; // ISO required cv-qualifer specializations template struct variant_alternative : add_cv_t> {}; template struct variant_alternative : add_volatile_t> {}; template struct variant_alternative : add_cv_t> {}; // variant_alternative_t template alias template using variant_alternative_t = typename variant_alternative::type; /////////////////////////////////////////////////////////////////////////// // 20.7.11, hash support // template struct hash > { size_t operator()(const variant& val) const { return static_cast(-0x42); } }; /////////////////////////////////////////////////////////////////////////// // get_if // template EA_CONSTEXPR add_pointer_t>> get_if(variant* pv) EA_NOEXCEPT { static_assert(I < sizeof...(Types), "get_if is ill-formed if I is not a valid index in the variant typelist"); using return_type = add_pointer_t>>; return (!pv || pv->index() != I) ? nullptr : pv->mStorage.template get_as(); } template EA_CONSTEXPR add_pointer_t>> get_if(const variant* pv) EA_NOEXCEPT { static_assert(I < sizeof...(Types), "get_if is ill-formed if I is not a valid index in the variant typelist"); using return_type = add_pointer_t>>; return (!pv || pv->index() != I) ? nullptr : pv->mStorage.template get_as(); } template > EA_CONSTEXPR add_pointer_t get_if(variant* pv) EA_NOEXCEPT { return get_if(pv); } template > EA_CONSTEXPR add_pointer_t get_if(const variant* pv) EA_NOEXCEPT { return get_if(pv); } /////////////////////////////////////////////////////////////////////////// // get // template EA_CONSTEXPR variant_alternative_t>& get(variant& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); using return_type = add_pointer_t>>; EASTL_ASSERT(v.index() == I); return *v.mStorage.template get_as(); } template EA_CONSTEXPR variant_alternative_t>&& get(variant&& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); using return_type = add_pointer_t>>; EASTL_ASSERT(v.index() == I); return eastl::move(*v.mStorage.template get_as()); } template EA_CONSTEXPR const variant_alternative_t>& get(const variant& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); using return_type = add_pointer_t>>; EASTL_ASSERT(v.index() == I); return *v.mStorage.template get_as(); } template EA_CONSTEXPR const variant_alternative_t>&& get(const variant&& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); using return_type = add_pointer_t>>; EASTL_ASSERT(v.index() == I); return eastl::move(*v.mStorage.template get_as()); } template > EA_CONSTEXPR T& get(variant& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); return get(v); } template > EA_CONSTEXPR T&& get(variant&& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); return get(eastl::move(v)); } template > EA_CONSTEXPR const T& get(const variant& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); return get(v); } template > EA_CONSTEXPR const T&& get(const variant&& v) { static_assert(I < sizeof...(Types), "get is ill-formed if I is not a valid index in the variant typelist"); return get(v); } /////////////////////////////////////////////////////////////////////////// // 20.7.4, value access // template > EA_CONSTEXPR bool holds_alternative(const variant& v) EA_NOEXCEPT { // ssize_t template parameter because the value can be negative return I == variant_npos ? false : (v.index() == I); } /////////////////////////////////////////////////////////////////////////// // 20.7.2, variant // template class variant { static_assert(sizeof...(Types) > 0, "variant must have at least 1 type (empty variants are ill-formed)"); static_assert(disjunction_v...> == false, "variant does not allow void as an alternative type"); static_assert(disjunction_v...> == false, "variant does not allow references as an alternative type"); static_assert(disjunction_v...> == false, "variant does not allow arrays as an alternative type"); using variant_index_t = size_t; using variant_storage_t = variant_storage...>, Types...>; using T_0 = variant_alternative_t<0, variant>; // alias for the 1st type in the variadic pack /////////////////////////////////////////////////////////////////////////// // variant data members // variant_index_t mIndex; variant_storage_t mStorage; public: /////////////////////////////////////////////////////////////////////////// // 20.7.2.1, constructors // // Only participates in overload resolution when the first alternative is default constructible template >> EA_CONSTEXPR variant() EA_NOEXCEPT : mIndex(variant_npos), mStorage() { mIndex = static_cast(0); mStorage.template set_as(); } // Only participates in overload resolution if is_copy_constructible_v is true for all T_i in Types.... template ...>, typename = enable_if_t> // add a dependent type to enable sfinae variant(const variant& other) { if (this != &other) { mIndex = other.mIndex; mStorage = other.mStorage; } } // Only participates in overload resolution if is_move_constructible_v is true for all T_i in Types... template ...>, typename = enable_if_t> // add a dependent type to enable sfinae EA_CONSTEXPR variant(variant&& other) EA_NOEXCEPT(conjunction_v...>) : mIndex(variant_npos), mStorage() { if(this != &other) { mIndex = other.mIndex; mStorage = eastl::move(other.mStorage); } } // Conversion constructor template >, typename = enable_if_t, variant>>, size_t I = meta::get_type_index_v, Types...>> EA_CONSTEXPR variant(T&& t) EA_NOEXCEPT(is_nothrow_constructible_v) : mIndex(variant_npos), mStorage() { static_assert(I >= 0, "T not found in type-list."); static_assert((meta::type_count_v == 1), "function overload is not unique - duplicate types in type list"); mIndex = static_cast(I); mStorage.template set_as(eastl::forward(t)); } /////////////////////////////////////////////////////////////////////////// // 20.7.2.1, in_place_t constructors // template < class T, class... Args, class = enable_if_t, is_constructible>, T>> EA_CPP14_CONSTEXPR explicit variant(in_place_type_t, Args&&... args) : variant(in_place>, eastl::forward(args)...) {} template < class T, class U, class... Args, class = enable_if_t, is_constructible>, T>> EA_CPP14_CONSTEXPR explicit variant(in_place_type_t, std::initializer_list il, Args&&... args) : variant(in_place>, il, eastl::forward(args)...) {} template , is_constructible, Args...>>>> EA_CPP14_CONSTEXPR explicit variant(in_place_index_t, Args&&... args) : mIndex(I) { mStorage.template set_as>(eastl::forward(args)...); } template , is_constructible, Args...>>>> EA_CPP14_CONSTEXPR explicit variant(in_place_index_t, std::initializer_list il, Args&&... args) : mIndex(I) { mStorage.template set_as>(il, eastl::forward(args)...); } /////////////////////////////////////////////////////////////////////////// // 20.7.2.2, destructor // ~variant() = default; /////////////////////////////////////////////////////////////////////////// // 20.7.2.4, modifiers // // Equivalent to emplace(std::forward(args)...), where I is the zero-based index of T in Types.... // This overload only participates in overload resolution if std::is_constructible_v is true, and T // occurs exactly once in Types... template < class T, class... Args, size_t I = meta::get_type_index_v, typename = enable_if_t, meta::duplicate_type_check>>> decltype(auto) emplace(Args&&... args) { return emplace(eastl::forward(args)...); } // Equivalent to emplace(il, std::forward(args)...), where I is the zero-based index of T in Types.... // This overload only participates in overload resolution if std::is_constructible_v&, Args...> is true, and T occurs exactly once in Types... template , typename = enable_if_t&, Args...>, meta::duplicate_type_check>>> decltype(auto) emplace(std::initializer_list il, Args&&... args) { return emplace(il, eastl::forward(args)...); } // First, destroys the currently contained value (if any). Then direct-initializes the contained value as if // constructing a value of type T_I with the arguments std::forward(args).... If an exception is thrown, // *this may become valueless_by_exception. This overload only participates in overload resolution if // std::is_constructible_v is true. The behavior is undefined if I is not less than // sizeof...(Types). // template , typename = enable_if_t, meta::duplicate_type_check>>> variant_alternative_t& emplace(Args&&... args) { if (!valueless_by_exception()) mStorage.destroy(); mIndex = static_cast(I); mStorage.template set_as(eastl::forward(args)...); return *reinterpret_cast(&mStorage.mBuffer); } // First, destroys the currently contained value (if any). Then direct-initializes the contained value as if // constructing a value of type T_I with the arguments il, std::forward(args).... If an exception is // thrown, *this may become valueless_by_exception. This overload only participates in overload resolution if // std::is_constructible_v&, Args...> is true. The behavior is undefined if I is not // less than sizeof...(Types). // template , typename = enable_if_t&, Args...>, meta::duplicate_type_check>>> variant_alternative_t& emplace(std::initializer_list il, Args&&... args) { if (!valueless_by_exception()) mStorage.destroy(); mIndex = static_cast(I); mStorage.template set_as(il, eastl::forward(args)...); return *reinterpret_cast(&mStorage.mBuffer); } /////////////////////////////////////////////////////////////////////////// // 20.7.2.3, assignment // template >, ssize_t I = meta::get_type_index_v, Types...>, typename = enable_if_t, variant> && eastl::is_assignable_v && eastl::is_constructible_v>> EA_CPP14_CONSTEXPR variant& operator=(T&& t) EA_NOEXCEPT(conjunction_v, is_nothrow_constructible>) { static_assert(I >= 0, "T not found in type-list."); static_assert((meta::type_count_v == 1), "function overload is not unique - duplicate types in type list"); if (!valueless_by_exception()) mStorage.destroy(); mIndex = static_cast(I); mStorage.template set_as(eastl::forward(t)); return *this; } // Only participates in overload resolution if is_copy_constructible_v && is_copy_assignable_v is true // for all T_i in Types.... template ...>, conjunction...>>, typename = enable_if_t> // add a dependent type to enable sfinae variant& operator=(const variant& other) { if (this != &other) { mIndex = other.mIndex; mStorage = other.mStorage; } return *this; } // Only participates in overload resolution if is_move_constructible_v && is_move_assignable_v is true for all T_i in Types.... template ...>, conjunction...>>, typename = enable_if_t> // add a dependent type to enable sfinae variant& operator=(variant&& other) EA_NOEXCEPT(conjunction_v...>, conjunction...>>) { if (this != &other) { mIndex = eastl::move(other.mIndex); mStorage = eastl::move(other.mStorage); } return *this; } /////////////////////////////////////////////////////////////////////////// // 20.7.2.5, value status // EA_CONSTEXPR size_t index() const EA_NOEXCEPT { return valueless_by_exception() ? variant_npos : mIndex; } EA_CONSTEXPR bool valueless_by_exception() const EA_NOEXCEPT { return mIndex == variant_npos; } /////////////////////////////////////////////////////////////////////////// // 20.7.2.6, swap // void swap(variant& other) EA_NOEXCEPT(conjunction_v..., is_nothrow_swappable...>) { eastl::swap(mIndex, other.mIndex); eastl::swap(mStorage, other.mStorage); } private: // NOTE(rparolin): get_if accessors require internal access to the variant storage class template friend EA_CONSTEXPR add_pointer_t< variant_alternative_t>> get_if( variant* pv) EA_NOEXCEPT; template friend EA_CONSTEXPR add_pointer_t>> get_if(const variant* pv) EA_NOEXCEPT; // NOTE(rparolin): get accessors require internal access to the variant storage class template friend EA_CONSTEXPR variant_alternative_t>& get(variant& v); template friend EA_CONSTEXPR variant_alternative_t>&& get(variant&& v); template friend EA_CONSTEXPR const variant_alternative_t>& get(const variant& v); template friend EA_CONSTEXPR const variant_alternative_t>&& get(const variant&& v); }; /////////////////////////////////////////////////////////////////////////// // 20.7.9, swap // template void swap(variant& lhs, variant& rhs) EA_NOEXCEPT(EA_NOEXCEPT(lhs.swap(rhs))) { lhs.swap(rhs); } // visit is a bit convoluted, in order to fulfill a few requirements: // - It must support visiting multiple variants using a single visitor and a single function call. The // visitor in this case should have one function for each possible combination of types: // // struct MyVisitor { // void operator()(int, int); // void operator()(string, string); // void operator()(int, string); // void operator()(string, int); // }; // // variant a = 42; // variant b = "hello"; // visit(MyVisitor{}, a, b); // calls MyVisitor::operator()(int, string) // // - It must be declared constexpr // - It must be constant-time for the case of visiting a single variant // - It must allow different return types in the visitor, as long as they are all convertible // // visitor_caller is responsible for the mechanics of visit. Each visitor_caller creates an array of // functions which call get() on the variant (where I is the array index), then add the returned reference // to a tuple of arguments. The final visitor_caller calls invoke() with the visitor and the unpacked // arguments. // // This allows us to look up each appropriate get() function in constant time using the variant's index. template struct visitor_caller { // @visitor, @variant and @variants are all the arguments to the initial visit() function. // @args is the tuple of arguments which have been retrieved by any previous visitor_callers. // // The two unnamed index_sequence parameters let us deduce two different sets of indices // as parameter packs - one for the arguments and one for the array of call_next functions. // This is necessary so we can create the constexpr array of functions which call // get(variant) based on the array index, and so we can unpack the final of arguments by // calling get(args) for each index in args. template static decltype(auto) EA_CONSTEXPR call_next(Visitor&& visitor, index_sequence, index_sequence, ArgsTuple&& args, Variant&& variant, Variants&&... variants) { // Call the appropriate get() function on the variant, and pack the result into a new tuple along with // all of the previous arguments. Then call the next visitor_caller with the new argument added, // and the current variant removed. return visitor_caller::call( eastl::forward(visitor), index_sequence(), index_sequence(), eastl::make_tuple(get(eastl::forward(args))..., get(eastl::forward(variant))), eastl::forward(variants)... ); } // Arguments are the same as for call_next (see above). template static decltype(auto) EA_CPP14_CONSTEXPR call(Visitor&& visitor, index_sequence, index_sequence, ArgsTuple&& args, Variant&& variant, Variants&&... variants) { // Deduce the type of the inner array of call_next functions using return_type = decltype(call_next<0>( eastl::forward(visitor), index_sequence(), index_sequence(), eastl::forward(args), eastl::forward(variant), eastl::forward(variants)...) ); using next_type = return_type (*)( Visitor&&, index_sequence, index_sequence, ArgsTuple&&, Variant&&, Variants&&... ); // Create an array of call_next<0>, call_next<1>, ... , call_next // where N = variant_size. EA_CPP14_CONSTEXPR next_type next[] = { static_cast(call_next)... }; // call_next() with the correct index for the variant. return next[variant.index()]( eastl::forward(visitor), index_sequence(), index_sequence(), eastl::forward(args), eastl::forward(variant), eastl::forward(variants)... ); } }; template struct visitor_caller { // Invoke the correct visitor for a given variant index, and call the correct get() function to retrieve // the argument. Unpack any additional arguments from earlier visitor_callers (see above). template static decltype(auto) EA_CONSTEXPR invoke_visitor(Visitor&& visitor, index_sequence, ArgsTuple&& args, Variant&& variant) { return static_cast(invoke( eastl::forward(visitor), get(eastl::forward(args))..., get(eastl::forward(variant)) )); } // The final call() in the recursion. // // By this point, expands to <0 .. N - 2> where N is the number of arguments to the // final invoke() call. This corresponds to each element in @args, so `get(args)...` // expands to `get<0>(args), get<1>(args), ... , get(args)`. The final argument is selected // based on the final array index, leaving us with a sequence of arguments from 0 .. N - 1. // // is the same as in earlier calls - it expands to <0 .. I - 1> where I is the // number of alternatives in the variant. This lets us call the correct `get` based on the // final variant index, as we did for all earlier calls. template static decltype(auto) EA_CPP14_CONSTEXPR call(Visitor&& visitor, index_sequence, index_sequence, ArgsTuple&& args, Variant&& variant) { // MSVC isn't able to handle the nested pack expansion required here, so we have to just use the // return type of the first visitor function instead of the common_type of all possible visitor // functions. This means we can't handle the case where visitor functions return different (but // compatible) types. This is unlikely to be a common case, but we might be able to get around it // if it's a big issue. // // TODO: we should reevaluate this on future compiler releases #if defined(EA_COMPILER_MSVC) using return_type = invoke_result_t(args))..., decltype(get<0>(variant))>; #else // If we're on a compiler that can take it, determine the common_type between all possible visitor // invocations. using return_type = common_type_t< invoke_result_t(args))..., decltype(get(variant))>... >; #endif using caller_type = return_type (*)(Visitor&&, index_sequence, ArgsTuple&&, Variant&&); // Create the final array of invoke_visitor<0>, invoke_visitor<1>, ... , invoke_visitor // where N = variant_size EA_CPP14_CONSTEXPR caller_type callers[] = { invoke_visitor... }; return callers[eastl::forward(variant).index()]( eastl::forward(visitor), index_sequence(), eastl::forward(args), eastl::forward(variant) ); } }; /////////////////////////////////////////////////////////////////////////// // 20.7.6, visitation // // Example: // struct MyVisitor // { // auto operator()(int) {}; // auto operator()(long) {}; // auto operator()(string) {}; // }; // // variant v = "Hello, Variant"; // visit(MyVisitor{}, v); // calls MyVisitor::operator()(string) {} // // visit // template EA_CONSTEXPR decltype(auto) visit(Visitor&& visitor, Variants&&... variants) { static_assert(sizeof...(Variants) > 0, "at least one variant instance must be passed as an argument to the visit function"); using variant_type = remove_reference_t>; static_assert(conjunction_v>...>, "all variants passed to eastl::visit() must have the same type"); return visitor_caller::call( eastl::forward(visitor), index_sequence<>(), make_index_sequence>(), tuple<>(), eastl::forward(variants)... ); } /////////////////////////////////////////////////////////////////////////// // 20.7.5, relational operators // namespace internal { template EA_CPP14_CONSTEXPR bool Compare(const variant& lhs, const variant& rhs, Predicate predicate) { return visit(predicate, lhs, rhs); } // For variant visitation, we need to have a comparison function for all possible combinations of types, // eg. for variant, our comparator needs: // // bool operator()(int, int); // bool operator()(int, string); // bool operator()(string, int); // bool operator()(string, string); // // Even though we never call the mixed-type versions of these functions when comparing variants, we // need them in order to compile visit(). So this struct forwards the good comparisons to the appropriate // comparison, and asserts that we never call the bad comparisons. template struct variant_comparison : public C { template , eastl::decay_t>>> auto operator()(const A& a, const B& b) { return C::operator()(a, b); } template , eastl::decay_t>>> bool operator()(const A&, const B&) { EASTL_ASSERT_MSG(false, "eastl::variant<> comparison function called on two different types at different indices! This is a library bug! Please file bug report."); return false; } }; } // namespace internal /////////////////////////////////////////////////////////////////////////// // 20.7.5, relational operators // template EA_CPP14_CONSTEXPR bool operator==(const variant& lhs, const variant& rhs) { if (lhs.index() != rhs.index()) return false; if (lhs.valueless_by_exception()) return true; return internal::Compare(lhs, rhs, internal::variant_comparison>{}); } template EA_CPP14_CONSTEXPR bool operator<(const variant& lhs, const variant& rhs) { if (rhs.valueless_by_exception()) return false; if (lhs.valueless_by_exception()) return true; if (lhs.index() < rhs.index()) return true; if (lhs.index() > rhs.index()) return false; return internal::Compare(lhs, rhs, internal::variant_comparison>{}); } template EA_CPP14_CONSTEXPR bool operator!=(const variant& lhs, const variant& rhs) { if (lhs.index() != rhs.index()) return true; if (lhs.valueless_by_exception()) return false; return internal::Compare(lhs, rhs, internal::variant_comparison>{}); } template EA_CPP14_CONSTEXPR bool operator>(const variant& lhs, const variant& rhs) { if (lhs.valueless_by_exception()) return false; if (rhs.valueless_by_exception()) return true; if (lhs.index() > rhs.index()) return true; if (lhs.index() < rhs.index()) return false; return internal::Compare(lhs, rhs, internal::variant_comparison>{}); } template EA_CPP14_CONSTEXPR bool operator<=(const variant& lhs, const variant& rhs) { if (rhs.valueless_by_exception()) return true; if (lhs.valueless_by_exception()) return false; if (lhs.index() < rhs.index()) return true; if (lhs.index() > rhs.index()) return false; return internal::Compare(lhs, rhs, internal::variant_comparison>{}); } template EA_CPP14_CONSTEXPR bool operator>=(const variant& lhs, const variant& rhs) { if (rhs.valueless_by_exception()) return true; if (lhs.valueless_by_exception()) return false; if (lhs.index() > rhs.index()) return true; if (lhs.index() < rhs.index()) return false; return internal::Compare(lhs, rhs, internal::variant_comparison>{}); } } // namespace eastl EA_RESTORE_VC_WARNING() #endif // EASTL_VARIANT_H