//============================================================================ // Copyright (c) Kitware, Inc. // All rights reserved. // See LICENSE.txt for details. // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ #if !defined(VTK_M_DEVICE) || !defined(VTK_M_NAMESPACE) #error VariantImpl.h must be included from Variant.h // Some defines to make my IDE happy. #define VTK_M_DEVICE #define VTK_M_NAMESPACE tmp #endif #include #include #include namespace vtkm { namespace VTK_M_NAMESPACE { // Forward declaration template class Variant; namespace detail { // -------------------------------------------------------------------------------- // Helper classes for Variant template struct VariantUnionToListImpl; template struct VariantUnionToListImpl> { using type = vtkm::List; }; template struct VariantUnionToListImpl> { using type = vtkm::List; }; template using VariantUnionToList = typename VariantUnionToListImpl::type>::type; struct VariantCopyConstructFunctor { template VTK_M_DEVICE void operator()(const T& src, UnionType& destUnion) const noexcept { constexpr vtkm::IdComponent Index = vtkm::ListIndexOf, T>::value; // If we are using this functor, we can assume the union does not hold a valid type. new (&VariantUnionGet(destUnion)) T(src); } }; struct VariantCopyFunctor { template VTK_M_DEVICE void operator()(const T& src, UnionType& destUnion) const noexcept { constexpr vtkm::IdComponent Index = vtkm::ListIndexOf, T>::value; // If we are using this functor, we can assume the union holds type T. this->DoCopy( src, VariantUnionGet(destUnion), typename std::is_copy_assignable::type{}); } template VTK_M_DEVICE void DoCopy(const T& src, T& dest, std::true_type) const noexcept { dest = src; } template VTK_M_DEVICE void DoCopy(const T& src, T& dest, std::false_type) const noexcept { if (&src != &dest) { // Do not have an assignment operator, so destroy the old object and create a new one. dest.~T(); new (&dest) T(src); } else { // Objects are already the same. } } }; struct VariantDestroyFunctor { template VTK_M_DEVICE void operator()(T& src) const noexcept { src.~T(); } }; template struct VariantCheckType { // We are currently not allowing reference types (e.g. FooType&) or pointer types (e.g. FooType*) // in Variant objects. References and pointers can fail badly when things are passed around // devices. If you get a compiler error here, consider removing the reference or using something // like std::decay to remove qualifiers on the type. (We may decide to do that automatically in // the future.) VTKM_STATIC_ASSERT_MSG(!std::is_reference::value, "References are not allowed in VTK-m Variant."); VTKM_STATIC_ASSERT_MSG(!std::is_pointer::value, "Pointers are not allowed in VTK-m Variant."); }; template struct VariantTriviallyCopyable; template struct VariantTriviallyCopyable> : AllTriviallyCopyable { }; template struct VariantTriviallyConstructible; template struct VariantTriviallyConstructible> : AllTriviallyConstructible { }; // -------------------------------------------------------------------------------- // Variant superclass that defines its storage template struct VariantStorageImpl { VariantUnion Storage; vtkm::IdComponent Index; VariantStorageImpl() = default; VTK_M_DEVICE VariantStorageImpl(vtkm::internal::NullType dummy) : Storage({ dummy }) { } template using TypeAt = typename vtkm::ListAt, Index>; VTK_M_DEVICE vtkm::IdComponent GetIndex() const noexcept { return this->Index; } VTK_M_DEVICE bool IsValid() const noexcept { return (this->Index >= 0) && (this->Index < static_cast(sizeof...(Ts))); } VTK_M_DEVICE void Reset() noexcept { if (this->IsValid()) { this->CastAndCall(detail::VariantDestroyFunctor{}); this->Index = -1; } } template VTK_M_DEVICE auto CastAndCall(Functor&& f, Args&&... args) const noexcept(noexcept(f(std::declval&>(), args...))) -> decltype(f(std::declval&>(), args...)) { VTKM_ASSERT(this->IsValid()); return detail::VariantCastAndCallImpl( this->GetIndex(), std::forward(f), this->Storage, std::forward(args)...); } template VTK_M_DEVICE auto CastAndCall(Functor&& f, Args&&... args) noexcept( noexcept(f(std::declval&>(), args...))) -> decltype(f(std::declval&>(), args...)) { VTKM_ASSERT(this->IsValid()); return detail::VariantCastAndCallImpl( this->GetIndex(), std::forward(f), this->Storage, std::forward(args)...); } }; // -------------------------------------------------------------------------------- // Variant superclass that helps preserve trivially copyable and trivially constructable // properties where possible. template ::type, typename TriviallyCopyable = typename VariantTriviallyCopyable::type> struct VariantConstructorImpl; // Can trivially construct, deconstruct, and copy all data. (Probably all trivial classes.) template struct VariantConstructorImpl, std::true_type, std::true_type> : VariantStorageImpl { VariantConstructorImpl() = default; ~VariantConstructorImpl() = default; VariantConstructorImpl(const VariantConstructorImpl&) = default; VariantConstructorImpl(VariantConstructorImpl&&) = default; VariantConstructorImpl& operator=(const VariantConstructorImpl&) = default; VariantConstructorImpl& operator=(VariantConstructorImpl&&) = default; }; // Can trivially copy, but cannot trivially construct. Common if a class is simple but // initializes itself. template struct VariantConstructorImpl, std::false_type, std::true_type> : VariantStorageImpl { VTK_M_DEVICE VariantConstructorImpl() : VariantStorageImpl(vtkm::internal::NullType{}) { this->Index = -1; } // Any trivially copyable class is trivially destructable. ~VariantConstructorImpl() = default; VariantConstructorImpl(const VariantConstructorImpl&) = default; VariantConstructorImpl(VariantConstructorImpl&&) = default; VariantConstructorImpl& operator=(const VariantConstructorImpl&) = default; VariantConstructorImpl& operator=(VariantConstructorImpl&&) = default; }; // Cannot trivially copy. We assume we cannot trivially construct/destruct. template struct VariantConstructorImpl, construct_type, std::false_type> : VariantStorageImpl { VTK_M_DEVICE VariantConstructorImpl() : VariantStorageImpl(vtkm::internal::NullType{}) { this->Index = -1; } VTK_M_DEVICE ~VariantConstructorImpl() { this->Reset(); } VTK_M_DEVICE VariantConstructorImpl(const VariantConstructorImpl& src) noexcept : VariantStorageImpl(vtkm::internal::NullType{}) { if (src.IsValid()) { src.CastAndCall(VariantCopyConstructFunctor{}, this->Storage); } this->Index = src.Index; } VTK_M_DEVICE VariantConstructorImpl& operator=(const VariantConstructorImpl& src) noexcept { if (src.IsValid()) { if (this->GetIndex() == src.GetIndex()) { src.CastAndCall(detail::VariantCopyFunctor{}, this->Storage); } else { this->Reset(); src.CastAndCall(detail::VariantCopyConstructFunctor{}, this->Storage); this->Index = src.Index; } } else { this->Reset(); } return *this; } }; } // namespace detail template class Variant : detail::VariantConstructorImpl> { using Superclass = detail::VariantConstructorImpl>; // Type not used, but has the compiler check all the types for validity. using CheckTypes = vtkm::List...>; public: /// Type that converts to a std::integral_constant containing the index of the given type (or /// -1 if that type is not in the list). template using IndexOf = vtkm::ListIndexOf, T>; /// Returns the index for the given type (or -1 if that type is not in the list). /// template VTK_M_DEVICE static constexpr vtkm::IdComponent GetIndexOf() { return IndexOf::value; } /// Type that converts to the type at the given index. /// template using TypeAt = typename vtkm::ListAt, Index>; /// \brief Type that indicates whether another type can be stored in this Variant. /// /// If this templated type resolves to `std::true_type`, then the provided `T` can be /// represented in this `Variant`. Otherwise, the type resolves to `std::false_type`. /// template using CanStore = std::integral_constant::value >= 0)>; /// Returns whether the given type can be respresented in this Variant. /// template VTK_M_DEVICE static constexpr bool GetCanStore() { return CanStore::value; } /// The number of types representable by this Variant. /// static constexpr vtkm::IdComponent NumberOfTypes = vtkm::IdComponent{ sizeof...(Ts) }; /// Returns the index of the type of object this variant is storing. If no object is currently /// stored (i.e. the `Variant` is invalid), an invalid is returned. /// VTK_M_DEVICE vtkm::IdComponent GetIndex() const noexcept { return this->Index; } /// Returns true if this `Variant` is storing an object from one of the types in the template /// list, false otherwise. /// /// Note that if this `Variant` was not initialized with an object, the result of `IsValid` /// is undefined. The `Variant` could report itself as validly containing an object that /// is trivially constructed. /// VTK_M_DEVICE bool IsValid() const noexcept { return (this->Index >= 0) && (this->Index < NumberOfTypes); } /// Returns true if this `Variant` stores the given type /// template VTK_M_DEVICE bool IsType() const { return (this->GetIndex() == this->GetIndexOf()); } Variant() = default; ~Variant() = default; Variant(const Variant&) = default; Variant(Variant&&) = default; Variant& operator=(const Variant&) = default; Variant& operator=(Variant&&) = default; template VTK_M_DEVICE Variant(const T& src) noexcept { constexpr vtkm::IdComponent index = GetIndexOf(); // Might be a way to use an enable_if to enforce a proper type. VTKM_STATIC_ASSERT_MSG(index >= 0, "Attempting to put invalid type into a Variant"); this->Index = index; new (&this->Get()) T(src); } template VTK_M_DEVICE Variant& operator=(const T& src) { if (this->IsType()) { this->Get() = src; } else { this->Emplace(src); } return *this; } template VTK_M_DEVICE T& Emplace(Args&&... args) { constexpr vtkm::IdComponent I = GetIndexOf(); VTKM_STATIC_ASSERT_MSG(I >= 0, "Variant::Emplace called with invalid type."); return this->EmplaceImpl(std::forward(args)...); } template VTK_M_DEVICE T& Emplace(std::initializer_list il, Args&&... args) { constexpr vtkm::IdComponent I = GetIndexOf(); VTKM_STATIC_ASSERT_MSG(I >= 0, "Variant::Emplace called with invalid type."); return this->EmplaceImpl(il, std::forward(args)...); } template VTK_M_DEVICE TypeAt& Emplace(Args&&... args) { VTKM_STATIC_ASSERT_MSG((I >= 0) && (I < NumberOfTypes), "Variant::Emplace called with invalid index"); return this->EmplaceImpl, I>(std::forward(args)...); } template VTK_M_DEVICE TypeAt& Emplace(std::initializer_list il, Args&&... args) { VTKM_STATIC_ASSERT_MSG((I >= 0) && (I < NumberOfTypes), "Variant::Emplace called with invalid index"); return this->EmplaceImpl, I>(il, std::forward(args)...); } private: template VTK_M_DEVICE T& EmplaceImpl(Args&&... args) { this->Reset(); this->Index = I; return *(new (&this->Get()) T{ args... }); } template VTK_M_DEVICE T& EmplaceImpl(std::initializer_list il, Args&&... args) { this->Reset(); this->Index = I; return *(new (&this->Get()) T(il, args...)); } public: ///@{ /// Returns the value as the type at the given index. The behavior is undefined if the /// variant does not contain the value at the given index. /// template VTK_M_DEVICE TypeAt& Get() noexcept { VTKM_ASSERT(I == this->GetIndex()); return detail::VariantUnionGet(this->Storage); } template VTK_M_DEVICE const TypeAt& Get() const noexcept { VTKM_ASSERT(I == this->GetIndex()); return detail::VariantUnionGet(this->Storage); } ///@} ///@{ /// Returns the value as the given type. The behavior is undefined if the variant does not /// contain a value of the given type. /// template VTK_M_DEVICE T& Get() noexcept { return this->GetImpl(CanStore{}); } template VTK_M_DEVICE const T& Get() const noexcept { return this->GetImpl(CanStore{}); } ///@} private: template VTK_M_DEVICE T& GetImpl(std::true_type) { VTKM_ASSERT(this->IsType()); return detail::VariantUnionGet::value>(this->Storage); } template VTK_M_DEVICE const T& GetImpl(std::true_type) const { VTKM_ASSERT(this->IsType()); return detail::VariantUnionGet::value>(this->Storage); } // This function overload only gets created if you attempt to pull a type from a // variant that does not exist. Perhaps this should be a compile error, but there // are cases where you might create templated code that has a path that could call // this but never does. To make this case easier, do a runtime error (when asserts // are active) instead. template VTK_M_DEVICE T& GetImpl(std::false_type) const { VTKM_ASSERT(false && "Attempted to get a type from a variant that the variant does not contain."); // This will cause some _really_ nasty issues if you actually try to use the returned type. return *reinterpret_cast(0); } public: ///@{ /// Given a functor object, calls the functor with the contained object cast to the appropriate /// type. If extra \c args are given, then those are also passed to the functor after the cast /// object. If the functor returns a value, that value is returned from \c CastAndCall. /// /// The results are undefined if the Variant is not valid. /// template VTK_M_DEVICE auto CastAndCall(Functor&& f, Args&&... args) const noexcept(noexcept(f(std::declval&>(), args...))) { VTKM_ASSERT(this->IsValid()); return detail::VariantCastAndCallImpl( this->GetIndex(), std::forward(f), this->Storage, std::forward(args)...); } template VTK_M_DEVICE auto CastAndCall(Functor&& f, Args&&... args) noexcept(noexcept(f(std::declval&>(), args...))) { VTKM_ASSERT(this->IsValid()); return detail::VariantCastAndCallImpl( this->GetIndex(), std::forward(f), this->Storage, std::forward(args)...); } /// Destroys any object the Variant is holding and sets the Variant to an invalid state. This /// method is not thread safe. /// VTK_M_DEVICE void Reset() noexcept { if (this->IsValid()) { this->CastAndCall(detail::VariantDestroyFunctor{}); this->Index = -1; } } }; /// \brief Convert a `List` to a `Variant`. /// template using ListAsVariant = vtkm::ListApply; } } // namespace vtkm::VTK_M_NAMESPACE #undef VTK_M_DEVICE #undef VTK_M_NAMESPACE