// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. #pragma once #include #include #include #include #include "arrow/util/macros.h" #include "arrow/util/type_traits.h" namespace arrow { namespace util { /// \brief a std::variant-like discriminated union /// /// Simplifications from std::variant: /// /// - Strictly defaultable. The first type of T... should be nothrow default constructible /// and it will be used for default Variants. /// /// - Never valueless_by_exception. std::variant supports a state outside those specified /// by T... to which it can return in the event that a constructor throws. If a Variant /// would become valueless_by_exception it will instead return to its default state. /// /// - Strictly nothrow move constructible and assignable /// /// - Less sophisticated type deduction. std::variant("hello") will /// intelligently construct std::string while Variant("hello") will /// construct bool. /// /// - Either both copy constructible and assignable or neither (std::variant independently /// enables copy construction and copy assignment). Variant is copy constructible if /// each of T... is copy constructible and assignable. /// /// - Slimmer interface; several members of std::variant are omitted. /// /// - Throws no exceptions; if a bad_variant_access would be thrown Variant will instead /// segfault (nullptr dereference). /// /// - Mutable visit takes a pointer instead of mutable reference or rvalue reference, /// which is more conformant with our code style. template class Variant; namespace detail { template struct is_equality_comparable : std::false_type {}; template struct is_equality_comparable< T, typename std::enable_if() == std::declval()), bool>::value>::type> : std::true_type {}; template using conditional_t = typename std::conditional::type; template struct type_constant { using type = T; }; template struct first; template struct first { using type = H; }; template using decay_t = typename std::decay::type; template struct all : std::true_type {}; template struct all : conditional_t, std::false_type> {}; struct delete_copy_constructor { template struct type { type() = default; type(const type& other) = delete; type& operator=(const type& other) = delete; }; }; struct explicit_copy_constructor { template struct type { type() = default; type(const type& other) { static_cast(other).copy_to(this); } type& operator=(const type& other) { static_cast(this)->destroy(); static_cast(other).copy_to(this); return *this; } }; }; template struct VariantStorage { VariantStorage() = default; VariantStorage(const VariantStorage&) {} VariantStorage& operator=(const VariantStorage&) { return *this; } VariantStorage(VariantStorage&&) noexcept {} VariantStorage& operator=(VariantStorage&&) noexcept { return *this; } ~VariantStorage() { static_assert(offsetof(VariantStorage, data_) == 0, "(void*)&VariantStorage::data_ == (void*)this"); } typename arrow::internal::aligned_union<0, T...>::type data_; uint8_t index_ = 0; }; template struct VariantImpl; template struct VariantImpl> : VariantStorage { static void index_of() noexcept {} void destroy() noexcept {} void move_to(...) noexcept {} void copy_to(...) const {} template [[noreturn]] R visit_const(Visitor&& visitor) const { std::terminate(); } template [[noreturn]] R visit_mutable(Visitor&& visitor) { std::terminate(); } }; template struct VariantImpl, H, T...> : VariantImpl, T...> { using VariantType = Variant; using Impl = VariantImpl; static constexpr uint8_t kIndex = sizeof...(M) - sizeof...(T) - 1; VariantImpl() = default; using VariantImpl::VariantImpl; using Impl::operator=; using Impl::index_of; explicit VariantImpl(H value) { new (this) H(std::move(value)); this->index_ = kIndex; } VariantImpl& operator=(H value) { static_cast(this)->destroy(); new (this) H(std::move(value)); this->index_ = kIndex; return *this; } H& cast_this() { return *reinterpret_cast(this); } const H& cast_this() const { return *reinterpret_cast(this); } void move_to(VariantType* target) noexcept { if (this->index_ == kIndex) { new (target) H(std::move(cast_this())); target->index_ = kIndex; } else { Impl::move_to(target); } } // Templated to avoid instantiation in case H is not copy constructible template void copy_to(Void* generic_target) const { const auto target = static_cast(generic_target); try { if (this->index_ == kIndex) { new (target) H(cast_this()); target->index_ = kIndex; } else { Impl::copy_to(target); } } catch (...) { target->construct_default(); throw; } } void destroy() noexcept { if (this->index_ == kIndex) { if (!std::is_trivially_destructible::value) { cast_this().~H(); } } else { Impl::destroy(); } } static constexpr std::integral_constant index_of( const type_constant&) { return {}; } template R visit_const(Visitor&& visitor) const { if (this->index_ == kIndex) { return std::forward(visitor)(cast_this()); } return Impl::template visit_const(std::forward(visitor)); } template R visit_mutable(Visitor&& visitor) { if (this->index_ == kIndex) { return std::forward(visitor)(&cast_this()); } return Impl::template visit_mutable(std::forward(visitor)); } }; } // namespace detail template class Variant : detail::VariantImpl, T...>, detail::conditional_t< detail::all<(std::is_copy_constructible::value && std::is_copy_assignable::value)...>::value, detail::explicit_copy_constructor, detail::delete_copy_constructor>::template type> { template static constexpr uint8_t index_of() { return Impl::index_of(detail::type_constant{}); } using Impl = detail::VariantImpl, T...>; public: using default_type = typename util::detail::first::type; Variant() noexcept { construct_default(); } Variant(const Variant& other) = default; Variant& operator=(const Variant& other) = default; using Impl::Impl; using Impl::operator=; Variant(Variant&& other) noexcept { other.move_to(this); } Variant& operator=(Variant&& other) noexcept { this->destroy(); other.move_to(this); return *this; } ~Variant() { static_assert(offsetof(Variant, data_) == 0, "(void*)&Variant::data_ == (void*)this"); this->destroy(); } /// \brief Return the zero-based type index of the value held by the variant uint8_t index() const noexcept { return this->index_; } /// \brief Get a const pointer to the value held by the variant /// /// If the type given as template argument doesn't match, a null pointer is returned. template ()> const U* get() const noexcept { return index() == I ? reinterpret_cast(this) : NULLPTR; } /// \brief Get a pointer to the value held by the variant /// /// If the type given as template argument doesn't match, a null pointer is returned. template ()> U* get() noexcept { return index() == I ? reinterpret_cast(this) : NULLPTR; } /// \brief Replace the value held by the variant /// /// The intended type must be given as a template argument. /// The value is constructed in-place using the given function arguments. template ()> void emplace(A&&... args) try { this->destroy(); new (this) U(std::forward(args)...); this->index_ = I; } catch (...) { construct_default(); throw; } template ()> void emplace(std::initializer_list il, A&&... args) try { this->destroy(); new (this) U(il, std::forward(args)...); this->index_ = I; } catch (...) { construct_default(); throw; } /// \brief Swap with another variant's contents void swap(Variant& other) noexcept { // NOLINT google-runtime-references Variant tmp = std::move(other); other = std::move(*this); *this = std::move(tmp); } using Impl::visit_const; using Impl::visit_mutable; private: void construct_default() noexcept { new (this) default_type(); this->index_ = 0; } template friend struct detail::explicit_copy_constructor::type; template friend struct detail::VariantImpl; }; /// \brief Call polymorphic visitor on a const variant's value /// /// The visitor will receive a const reference to the value held by the variant. /// It must define overloads for each possible variant type. /// The overloads should all return the same type (no attempt /// is made to find a generalized return type). template ()( std::declval::default_type&>()))> R visit(Visitor&& visitor, const util::Variant& v) { return v.template visit_const(std::forward(visitor)); } /// \brief Call polymorphic visitor on a non-const variant's value /// /// The visitor will receive a pointer to the value held by the variant. /// It must define overloads for each possible variant type. /// The overloads should all return the same type (no attempt /// is made to find a generalized return type). template ()( std::declval::default_type*>()))> R visit(Visitor&& visitor, util::Variant* v) { return v->template visit_mutable(std::forward(visitor)); } /// \brief Get a const reference to the value held by the variant /// /// If the type given as template argument doesn't match, behavior is undefined /// (a null pointer will be dereferenced). template const U& get(const Variant& v) { return *v.template get(); } /// \brief Get a reference to the value held by the variant /// /// If the type given as template argument doesn't match, behavior is undefined /// (a null pointer will be dereferenced). template U& get(Variant& v) { return *v.template get(); } /// \brief Get a const pointer to the value held by the variant /// /// If the type given as template argument doesn't match, a nullptr is returned. template const U* get_if(const Variant* v) { return v->template get(); } /// \brief Get a pointer to the value held by the variant /// /// If the type given as template argument doesn't match, a nullptr is returned. template U* get_if(Variant* v) { return v->template get(); } namespace detail { template struct VariantsEqual { template bool operator()(const U& r) const { return get(l_) == r; } const Variant& l_; }; } // namespace detail template ::value...>::value>> bool operator==(const Variant& l, const Variant& r) { if (l.index() != r.index()) return false; return visit(detail::VariantsEqual{l}, r); } template auto operator!=(const Variant& l, const Variant& r) -> decltype(l == r) { return !(l == r); } /// \brief Return whether the variant holds a value of the given type template bool holds_alternative(const Variant& v) { return v.template get(); } } // namespace util } // namespace arrow