Handle Variant::Get for types not supported by the Variant

Previously, if you called `Get` on a `Variant` with a type that is not
in the list of types supported by the `Variant`, that would attempt to
look up the type at index `-1` and could spin the compiler into an
endless loop.

Instead, check for the case where you are attempting to get a type from
the `Variant` not listed in its templat arguments. In this case, instead
of producing a compiler error, produce a runtime error. Although this
increases the possibility that a bad compile path is being generated, it
simplifies creating templated code that produces cases we don't care
about.
This commit is contained in:
Kenneth Moreland 2021-10-04 10:19:25 -06:00
parent a607679cb1
commit e9da343109
2 changed files with 56 additions and 4 deletions

@ -230,6 +230,11 @@ void TestIndexing()
VTKM_STATIC_ASSERT((std::is_same<VariantType::TypeAt<27>, TypePlaceholder<27>>::value));
VTKM_STATIC_ASSERT((std::is_same<VariantType::TypeAt<28>, TypePlaceholder<28>>::value));
VTKM_STATIC_ASSERT((std::is_same<VariantType::TypeAt<29>, TypePlaceholder<29>>::value));
VTKM_TEST_ASSERT(VariantType::CanStore<TypePlaceholder<2>>::value);
VTKM_TEST_ASSERT(!VariantType::CanStore<TypePlaceholder<100>>::value);
VTKM_TEST_ASSERT(variant.GetCanStore<TypePlaceholder<3>>());
VTKM_TEST_ASSERT(!variant.GetCanStore<TypePlaceholder<101>>());
}
void TestTriviallyCopyable()
@ -352,6 +357,9 @@ void TestGet()
VTKM_TEST_ASSERT(variant.Get<2>() == expectedValue);
VTKM_TEST_ASSERT(variant.Get<vtkm::Id>() == expectedValue);
// This line should compile, but will assert if you actually try to run it.
//variant.Get<TypePlaceholder<100>>();
}
{

@ -305,6 +305,22 @@ public:
template <vtkm::IdComponent Index>
using TypeAt = typename vtkm::ListAt<vtkm::List<Ts...>, 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 <typename T>
using CanStore = std::integral_constant<bool, (IndexOf<T>::value >= 0)>;
/// Returns whether the given type can be respresented in this Variant.
///
template <typename T>
VTK_M_DEVICE static constexpr bool GetCanStore()
{
return CanStore<T>::value;
}
/// The number of types representable by this Variant.
///
static constexpr vtkm::IdComponent NumberOfTypes = vtkm::IdComponent{ sizeof...(Ts) };
@ -434,18 +450,46 @@ public:
template <typename T>
VTK_M_DEVICE T& Get() noexcept
{
VTKM_ASSERT(this->GetIndexOf<T>() == this->GetIndex());
return detail::VariantUnionGet<IndexOf<T>::value>(this->Storage);
return this->GetImpl<T>(CanStore<T>{});
}
template <typename T>
VTK_M_DEVICE const T& Get() const noexcept
{
VTKM_ASSERT(this->GetIndexOf<T>() == this->GetIndex());
return detail::VariantUnionGet<IndexOf<T>::value>(this->Storage);
return this->GetImpl<T>(CanStore<T>{});
}
//@}
private:
template <typename T>
VTK_M_DEVICE T& GetImpl(std::true_type)
{
VTKM_ASSERT(this->GetIndexOf<T>() == this->GetIndex());
return detail::VariantUnionGet<IndexOf<T>::value>(this->Storage);
}
template <typename T>
VTK_M_DEVICE const T& GetImpl(std::true_type) const
{
VTKM_ASSERT(this->GetIndexOf<T>() == this->GetIndex());
return detail::VariantUnionGet<IndexOf<T>::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 <typename T>
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<T*>(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