//============================================================================ // 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. //============================================================================ #ifndef vtk_m_ArrayHandleDecorator_h #define vtk_m_ArrayHandleDecorator_h #include #include #include #include #include #include #include #include #include #include #include namespace vtkm { namespace cont { namespace internal { namespace decor { // Generic InverseFunctor implementation that does nothing. struct NoOpInverseFunctor { NoOpInverseFunctor() = default; template VTKM_EXEC_CONT NoOpInverseFunctor(Ts...) { } template VTKM_EXEC_CONT void operator()(vtkm::Id, VT) const { } }; } // namespace decor // The portal for ArrayHandleDecorator. Get calls FunctorType::operator(), and // Set calls InverseFunctorType::operator(), but only if the DecoratorImpl // provides an inverse. template class VTKM_ALWAYS_EXPORT ArrayPortalDecorator { public: using ValueType = ValueType_; using FunctorType = FunctorType_; using InverseFunctorType = InverseFunctorType_; using ReadOnly = std::is_same; VTKM_EXEC_CONT ArrayPortalDecorator() {} VTKM_CONT ArrayPortalDecorator(FunctorType func, InverseFunctorType iFunc, vtkm::Id numValues) : Functor(func) , InverseFunctor(iFunc) , NumberOfValues(numValues) { } VTKM_EXEC_CONT vtkm::Id GetNumberOfValues() const { return this->NumberOfValues; } VTKM_EXEC_CONT ValueType Get(vtkm::Id index) const { return this->Functor(index); } template ::type> VTKM_EXEC_CONT void Set(vtkm::Id index, const ValueType& value) const { this->InverseFunctor(index, value); } private: FunctorType Functor; InverseFunctorType InverseFunctor; vtkm::Id NumberOfValues; }; namespace decor { // Ensures that all types in variadic container ArrayHandleList are subclasses // of ArrayHandleBase. template using AllAreArrayHandles = brigand::all>; namespace detail { // Tests whether DecoratorImplT has a CreateInverseFunctor(Portals...) method. template struct IsFunctorInvertibleImpl; template class List, typename... PortalTs> struct IsFunctorInvertibleImpl> { private: template < typename T, typename U = decltype(std::declval().CreateInverseFunctor(std::declval()...))> static std::true_type InverseExistsTest(int); template static std::false_type InverseExistsTest(...); public: using type = decltype(InverseExistsTest(0)); }; // Tests whether DecoratorImplT has an AllocateSourceArrays(size, Arrays...) method. template struct IsDecoratorAllocatableImpl; template class List, typename... ArrayTs> struct IsDecoratorAllocatableImpl> { private: template < typename T, typename U = decltype(std::declval().AllocateSourceArrays(0, std::declval()...))> static std::true_type Exists(int); template static std::false_type Exists(...); public: using type = decltype(Exists(0)); }; // Tests whether DecoratorImplT has a ShrinkSourceArrays(size, Arrays...) method. template struct IsDecoratorShrinkableImpl; template class List, typename... ArrayTs> struct IsDecoratorShrinkableImpl> { private: template < typename T, typename U = decltype(std::declval().ShrinkSourceArrays(0, std::declval()...))> static std::true_type Exists(int); template static std::false_type Exists(...); public: using type = decltype(Exists(0)); }; // Deduces the type returned by DecoratorImplT::CreateFunctor when given // the specified portals. template struct GetFunctorTypeImpl; template class List, typename... PortalTs> struct GetFunctorTypeImpl> { using type = decltype(std::declval().CreateFunctor(std::declval()...)); }; // Deduces the type returned by DecoratorImplT::CreateInverseFunctor when given // the specified portals. If DecoratorImplT doesn't have a CreateInverseFunctor // method, a NoOp functor will be used instead. template struct GetInverseFunctorTypeImpl; template class List, typename... PortalTs> struct GetInverseFunctorTypeImpl> { using type = decltype(std::declval().CreateInverseFunctor(std::declval()...)); }; template struct GetInverseFunctorTypeImpl { using type = NoOpInverseFunctor; }; // Get appropriate portals from a source array. // See note below about using non-writable portals in invertible functors. // We need to sub in const portals when writable ones don't exist. template typename std::decay::type::WritePortalType GetPortalControlImpl(std::true_type, ArrayT&& array) { return array.WritePortal(); } template typename std::decay::type::ReadPortalType GetPortalControlImpl(std::false_type, ArrayT&& array) { return array.ReadPortal(); } template typename std::decay::type::template ExecutionTypes::Portal GetPortalInPlaceImpl(std::true_type, ArrayT&& array, Device, vtkm::cont::Token& token) { return array.PrepareForInPlace(Device{}, token); } template typename std::decay::type::template ExecutionTypes::PortalConst GetPortalInPlaceImpl(std::false_type, ArrayT&& array, Device, vtkm::cont::Token& token) { // ArrayT is read-only -- prepare for input instead. return array.PrepareForInput(Device{}, token); } template typename std::decay::type::template ExecutionTypes::Portal GetPortalOutputImpl(std::true_type, ArrayT&& array, Device, vtkm::cont::Token& token) { // Prepare these for inplace usage instead -- we'll likely need to read // from these in addition to writing. return array.PrepareForInPlace(Device{}, token); } template typename std::decay::type::template ExecutionTypes::PortalConst GetPortalOutputImpl(std::false_type, ArrayT&& array, Device, vtkm::cont::Token& token) { // ArrayT is read-only -- prepare for input instead. return array.PrepareForInput(Device{}, token); } } // namespace detail // Get portal types: // We allow writing to an AHDecorator if *any* of the ArrayHandles are writable. // This means we have to avoid calling PrepareForOutput, etc on non-writable // array handles, since these may throw. On non-writable handles, use the // const array handles so we can at least read from them in the inverse // functors. template ::type::WritePortalType, typename PortalConst = typename std::decay::type::ReadPortalType> using GetPortalControlType = typename brigand::if_, Portal, PortalConst>::type; template using GetPortalConstControlType = typename std::decay::type::ReadPortalType; template ::type::template ExecutionTypes::Portal, typename PortalConst = typename std::decay::type::template ExecutionTypes::PortalConst> using GetPortalExecutionType = typename brigand::if_, Portal, PortalConst>::type; template using GetPortalConstExecutionType = typename std::decay::type::template ExecutionTypes::PortalConst; // Get portal objects: // See note above -- we swap in const portals sometimes. template GetPortalControlType::type> WritePortal(ArrayT&& array) { return detail::GetPortalControlImpl(IsWritableArrayHandle{}, std::forward(array)); } template GetPortalConstControlType::type> ReadPortal(const ArrayT& array) { return array.ReadPortal(); } template GetPortalConstExecutionType::type, Device> GetPortalInput(const ArrayT& array, Device, vtkm::cont::Token& token) { return array.PrepareForInput(Device{}, token); } template GetPortalExecutionType::type, Device> GetPortalInPlace(ArrayT&& array, Device, vtkm::cont::Token& token) { return detail::GetPortalInPlaceImpl( IsWritableArrayHandle{}, std::forward(array), Device{}, token); } template GetPortalExecutionType::type, Device> GetPortalOutput(ArrayT&& array, Device, vtkm::cont::Token& token) { return detail::GetPortalOutputImpl( IsWritableArrayHandle{}, std::forward(array), Device{}, token); } // Equivalent to std::true_type if *any* portal in PortalList can be written to. // If all are read-only, std::false_type is used instead. template using AnyPortalIsWritable = typename brigand::any>::type; // Set to std::true_type if DecoratorImplT::CreateInverseFunctor can be called // with the supplied portals, or std::false_type otherwise. template using IsFunctorInvertible = typename detail::IsFunctorInvertibleImpl::type; // Set to std::true_type if DecoratorImplT::AllocateSourceArrays can be called // with the supplied arrays, or std::false_type otherwise. template using IsDecoratorAllocatable = typename detail::IsDecoratorAllocatableImpl::type; // Set to std::true_type if DecoratorImplT::ShrinkSourceArrays can be called // with the supplied arrays, or std::false_type otherwise. template using IsDecoratorShrinkable = typename detail::IsDecoratorShrinkableImpl::type; // std::true_type/std::false_type depending on whether the decorator impl has a // CreateInversePortal method AND any of the arrays are writable. template using CanWriteToFunctor = typename brigand::and_, AnyPortalIsWritable>::type; // The FunctorType for the provided implementation and portal types. template using GetFunctorType = typename detail::GetFunctorTypeImpl::type; // The InverseFunctorType for the provided implementation and portal types. // Will detect when inversion is not possible and return a NoOp functor instead. template using GetInverseFunctorType = typename detail::GetInverseFunctorTypeImpl, DecoratorImplT, PortalList>::type; // Convert a sequence of array handle types to a list of portals: // Some notes on this implementation: // - MSVC 2015 ICEs when using brigand::transform to convert a brigand::list // of arrayhandles to portals. So instead we pass the ArrayTs. // - Just using brigand::list...> fails, as // apparently that is an improper parameter pack expansion // - So we jump through some decltype/declval hoops here to get this to work: template using GetPortalConstControlList = brigand::list())))...>; template using GetPortalConstExecutionList = brigand::list(), Device{}, std::declval())))...>; template using GetPortalControlList = brigand::list())))...>; template using GetPortalExecutionList = brigand::list(), Device{}, std::declval())))...>; template struct DecoratorStorageTraits { using ArrayList = brigand::list; VTKM_STATIC_ASSERT_MSG(sizeof...(ArrayTs) > 0, "Must specify at least one source array handle for " "ArrayHandleDecorator. Consider using " "ArrayHandleImplicit instead."); // Need to check this here, since this traits struct is used in the // ArrayHandleDecorator superclass definition before any other // static_asserts could be used. VTKM_STATIC_ASSERT_MSG(decor::AllAreArrayHandles::value, "Trailing template parameters for " "ArrayHandleDecorator must be a list of ArrayHandle " "types."); using ArrayTupleType = vtkm::Tuple; // size_t integral constants that index ArrayTs: using IndexList = vtkmstd::make_index_sequence; // true_type/false_type depending on whether the decorator supports Allocate/Shrink: using IsAllocatable = IsDecoratorAllocatable; using IsShrinkable = IsDecoratorShrinkable; // Portal lists: // NOTE we have to pass the parameter pack here instead of using ArrayList // with brigand::transform, since that's causing MSVC 2015 to ice: using PortalControlList = GetPortalControlList; using PortalConstControlList = GetPortalConstControlList; template using PortalExecutionList = GetPortalExecutionList; template using PortalConstExecutionList = GetPortalConstExecutionList; // Functors: using FunctorControlType = GetFunctorType; using FunctorConstControlType = GetFunctorType; template using FunctorExecutionType = GetFunctorType>; template using FunctorConstExecutionType = GetFunctorType>; // Inverse functors: using InverseFunctorControlType = GetInverseFunctorType; using InverseFunctorConstControlType = NoOpInverseFunctor; template using InverseFunctorExecutionType = GetInverseFunctorType>; template using InverseFunctorConstExecutionType = NoOpInverseFunctor; // Misc: // ValueType is derived from DecoratorImplT::CreateFunctor(...)'s operator(). using ValueType = decltype(std::declval()(0)); // Decorator portals: using PortalControlType = ArrayPortalDecorator; using PortalConstControlType = ArrayPortalDecorator; template using PortalExecutionType = ArrayPortalDecorator, InverseFunctorExecutionType>; template using PortalConstExecutionType = ArrayPortalDecorator, InverseFunctorConstExecutionType>; // helper for constructing portals with the appropriate functors. This is // where we decide whether or not to call `CreateInverseFunctor` on the // implementation class. // Do not use these directly, they are helpers for the MakePortal[...] // methods below. template VTKM_CONT static typename std::enable_if::type CreatePortalDecorator(vtkm::Id numVals, const DecoratorImplT& impl, PortalTs&&... portals) { // Portal is read only: return { impl.CreateFunctor(std::forward(portals)...), typename DecoratorPortalType::InverseFunctorType{}, numVals }; } template VTKM_CONT static typename std::enable_if::type CreatePortalDecorator(vtkm::Id numVals, const DecoratorImplT& impl, PortalTs... portals) { // Portal is read/write: return { impl.CreateFunctor(portals...), impl.CreateInverseFunctor(portals...), numVals }; } // Static dispatch for calling AllocateSourceArrays on supported implementations: VTKM_CONT [[noreturn]] static void CallAllocate(std::false_type, const DecoratorImplT&, vtkm::Id, ArrayTs&...) { throw vtkm::cont::ErrorBadType("Allocate not supported by this ArrayHandleDecorator."); } VTKM_CONT static void CallAllocate(std::true_type, const DecoratorImplT& impl, vtkm::Id newSize, ArrayTs&... arrays) { impl.AllocateSourceArrays(newSize, arrays...); } // Static dispatch for calling ShrinkSourceArrays on supported implementations. VTKM_CONT [[noreturn]] static void CallShrink(std::false_type, const DecoratorImplT&, vtkm::Id, ArrayTs&...) { throw vtkm::cont::ErrorBadType("Shrink not supported by this ArrayHandleDecorator."); } VTKM_CONT static void CallShrink(std::true_type, const DecoratorImplT& impl, vtkm::Id newSize, ArrayTs&... arrays) { impl.ShrinkSourceArrays(newSize, arrays...); } // Portal construction methods. These actually create portals. template VTKM_CONT static PortalControlType MakePortalControl(const DecoratorImplT& impl, ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence) { return CreatePortalDecorator( numValues, impl, WritePortal(vtkm::Get(arrays))...); } template VTKM_CONT static PortalConstControlType MakePortalConstControl( const DecoratorImplT& impl, const ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence) { return CreatePortalDecorator( numValues, impl, ReadPortal(vtkm::Get(arrays))...); } template VTKM_CONT static PortalConstExecutionType MakePortalInput( const DecoratorImplT& impl, const ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence, Device dev, vtkm::cont::Token& token) { return CreatePortalDecorator>( numValues, impl, GetPortalInput(vtkm::Get(arrays), dev, token)...); } template VTKM_CONT static PortalExecutionType MakePortalInPlace( const DecoratorImplT& impl, ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence, Device dev, vtkm::cont::Token& token) { return CreatePortalDecorator>( numValues, impl, GetPortalInPlace(vtkm::Get(arrays), dev, token)...); } template VTKM_CONT static PortalExecutionType MakePortalOutput(const DecoratorImplT& impl, ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence, Device dev, vtkm::cont::Token& token) { return CreatePortalDecorator>( numValues, impl, GetPortalOutput(vtkm::Get(arrays), dev, token)...); } template VTKM_CONT static void AllocateSourceArrays(const DecoratorImplT& impl, ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence) { CallAllocate(IsAllocatable{}, impl, numValues, vtkm::Get(arrays)...); } template VTKM_CONT static void ShrinkSourceArrays(const DecoratorImplT& impl, ArrayTupleType& arrays, vtkm::Id numValues, vtkmstd::index_sequence) { CallShrink(IsShrinkable{}, impl, numValues, vtkm::Get(arrays)...); } }; } // end namespace decor template struct VTKM_ALWAYS_EXPORT StorageTagDecorator { }; template class Storage::ValueType, StorageTagDecorator> { using Traits = decor::DecoratorStorageTraits; using IndexList = typename Traits::IndexList; public: using ArrayTupleType = typename Traits::ArrayTupleType; using ValueType = typename Traits::ValueType; using PortalType = typename Traits::PortalControlType; using PortalConstType = typename Traits::PortalConstControlType; VTKM_CONT Storage() : Valid{ false } { } VTKM_CONT Storage(const DecoratorImplT& impl, const ArrayTupleType& arrayTuple, vtkm::Id numValues) : Implementation(impl) , ArrayTuple{ arrayTuple } , NumberOfValues(numValues) , Valid{ true } { } VTKM_CONT PortalType GetPortal() { VTKM_ASSERT(this->Valid); return Traits::MakePortalControl( this->Implementation, this->ArrayTuple, this->NumberOfValues, IndexList{}); } VTKM_CONT PortalConstType GetPortalConst() const { VTKM_ASSERT(this->Valid); return Traits::MakePortalConstControl( this->Implementation, this->ArrayTuple, this->NumberOfValues, IndexList{}); } VTKM_CONT vtkm::Id GetNumberOfValues() const { VTKM_ASSERT(this->Valid); return this->NumberOfValues; } VTKM_CONT void Allocate(vtkm::Id numValues) { VTKM_ASSERT(this->Valid); Traits::AllocateSourceArrays(this->Implementation, this->ArrayTuple, numValues, IndexList{}); // If the above call doesn't throw, update our state. this->NumberOfValues = numValues; } VTKM_CONT void Shrink(vtkm::Id numValues) { VTKM_ASSERT(this->Valid); Traits::ShrinkSourceArrays(this->Implementation, this->ArrayTuple, numValues, IndexList{}); // If the above call doesn't throw, update our state. this->NumberOfValues = numValues; } VTKM_CONT void ReleaseResources() { VTKM_ASSERT(this->Valid); // No-op. Again, could eventually be passed down to the implementation. } VTKM_CONT const ArrayTupleType& GetArrayTuple() const { VTKM_ASSERT(this->Valid); return this->ArrayTuple; } VTKM_CONT ArrayTupleType& GetArrayTuple() { VTKM_ASSERT(this->Valid); return this->ArrayTuple; } VTKM_CONT const DecoratorImplT& GetImplementation() const { VTKM_ASSERT(this->Valid); return this->Implementation; } VTKM_CONT DecoratorImplT& GetImplementation() { VTKM_ASSERT(this->Valid); return this->Implementation; } private: DecoratorImplT Implementation; ArrayTupleType ArrayTuple; vtkm::Id NumberOfValues; bool Valid; }; template struct DecoratorHandleTraits { using StorageTraits = decor::DecoratorStorageTraits; using ValueType = typename StorageTraits::ValueType; using StorageTag = StorageTagDecorator; using StorageType = vtkm::cont::internal::Storage; using Superclass = vtkm::cont::ArrayHandle; }; template class ArrayTransfer::ValueType, StorageTagDecorator, Device> { VTKM_IS_DEVICE_ADAPTER_TAG(Device); using HandleTraits = DecoratorHandleTraits; using Traits = typename HandleTraits::StorageTraits; using IndexList = typename Traits::IndexList; using StorageType = typename HandleTraits::StorageType; public: using ValueType = typename Traits::ValueType; using PortalControl = typename Traits::PortalControlType; using PortalConstControl = typename Traits::PortalConstControlType; using PortalExecution = typename Traits::template PortalExecutionType; using PortalConstExecution = typename Traits::template PortalConstExecutionType; VTKM_CONT ArrayTransfer(StorageType* storage) : Storage(storage) { } VTKM_CONT vtkm::Id GetNumberOfValues() const { return this->Storage->GetNumberOfValues(); } VTKM_CONT PortalConstExecution PrepareForInput(bool vtkmNotUsed(updateData), vtkm::cont::Token& token) const { return Traits::MakePortalInput(this->Storage->GetImplementation(), this->Storage->GetArrayTuple(), this->Storage->GetNumberOfValues(), IndexList{}, Device{}, token); } VTKM_CONT PortalExecution PrepareForInPlace(bool vtkmNotUsed(updateData), vtkm::cont::Token& token) { return Traits::MakePortalInPlace(this->Storage->GetImplementation(), this->Storage->GetArrayTuple(), this->Storage->GetNumberOfValues(), IndexList{}, Device{}, token); } VTKM_CONT PortalExecution PrepareForOutput(vtkm::Id, vtkm::cont::Token& token) { return Traits::MakePortalOutput(this->Storage->GetImplementation(), this->Storage->GetArrayTuple(), this->Storage->GetNumberOfValues(), IndexList{}, Device{}, token); } VTKM_CONT void RetrieveOutputData(StorageType* vtkmNotUsed(storage)) const { // Implementation of this method should be unnecessary. The internal // array handles should automatically retrieve the output data as // necessary. } VTKM_CONT void Shrink(vtkm::Id numValues) { this->Storage->Shrink(numValues); } VTKM_CONT void ReleaseResources() { // no-op } private: StorageType* Storage; }; } // namespace internal /// \brief A fancy ArrayHandle that can be used to modify the results from one /// or more source ArrayHandle. /// /// ArrayHandleDecorator is given a `DecoratorImplT` class and a list of one or /// more source ArrayHandles. There are no restrictions on the size or type of /// the source ArrayHandles. /// /// The decorator implementation class is described below: /// /// ``` /// struct ExampleDecoratorImplementation /// { /// /// // Takes one portal for each source array handle (only two shown). /// // Returns a functor that defines: /// // /// // VTKM_EXEC_CONT ValueType operator()(vtkm::Id id) const; /// // /// // which takes an index and returns a value which should be produced by /// // the source arrays somehow. This ValueType will be the ValueType of the /// // ArrayHandleDecorator. /// // /// // Both SomeFunctor::operator() and CreateFunctor must be const. /// // /// template /// VTKM_CONT /// SomeFunctor CreateFunctor(Portal1Type portal1, Portal2Type portal2) const; /// /// // Takes one portal for each source array handle (only two shown). /// // Returns a functor that defines: /// // /// // VTKM_EXEC_CONT void operator()(vtkm::Id id, ValueType val) const; /// // /// // which takes an index and a value, which should be used to modify one /// // or more of the source arrays. /// // /// // CreateInverseFunctor is optional; if not provided, the /// // ArrayHandleDecorator will be read-only. In addition, if all of the /// // source ArrayHandles are read-only, the inverse functor will not be used /// // and the ArrayHandleDecorator will be read only. /// // /// // Both SomeInverseFunctor::operator() and CreateInverseFunctor must be /// // const. /// // /// template /// VTKM_CONT /// SomeInverseFunctor CreateInverseFunctor(Portal1Type portal1, /// Portal2Type portal2) const; /// /// // Given a set of ArrayHandles and a size, implement what should happen /// // to the source ArrayHandles when Allocate() is called on the decorator /// // handle. /// // /// // AllocateSourceArrays is optional; if not provided, the /// // ArrayHandleDecorator will throw if its Allocate method is called. If /// // an implementation is present and doesn't throw, the /// // ArrayHandleDecorator's internal state is updated to show `size` as the /// // number of values. /// template /// VTKM_CONT /// void AllocateSourceArrays(vtkm::Id size, Array1Type array1, Array2Type array2) const; /// /// // Given a set of ArrayHandles and a size, implement what should happen to /// // the source ArrayHandles when Shrink() is called on the decorator handle. /// // /// // ShrinkSourceArrays is optional; if not provided, the /// // ArrayHandleDecorator will throw if its Shrink method is called. If /// // an implementation is present and doesn't throw, the /// // ArrayHandleDecorator's internal state is updated to show `size` as the /// // number of values. /// template /// VTKM_CONT /// void ShrinkSourceArrays(vtkm::Id size, Array1Type array1, Array2Type array2) const; /// /// }; /// ``` /// /// There are several example DecoratorImpl classes provided in the /// UnitTestArrayHandleDecorator test file. /// template class ArrayHandleDecorator : public internal::DecoratorHandleTraits::type, typename std::decay::type...>::Superclass { private: using Traits = internal::DecoratorHandleTraits::type, typename std::decay::type...>; using StorageType = typename Traits::StorageType; public: VTKM_ARRAY_HANDLE_SUBCLASS(ArrayHandleDecorator, (ArrayHandleDecorator::type, typename std::decay::type...>), (typename Traits::Superclass)); VTKM_CONT ArrayHandleDecorator(vtkm::Id numValues, const typename std::decay::type& impl, const typename std::decay::type&... arrays) : Superclass{ StorageType{ impl, vtkm::MakeTuple(arrays...), numValues } } { } }; /// Create an ArrayHandleDecorator with the specified number of values that /// uses the provided DecoratorImplT and source ArrayHandles. /// template VTKM_CONT ArrayHandleDecorator::type, typename std::decay::type...> make_ArrayHandleDecorator(vtkm::Id numValues, DecoratorImplT&& f, ArrayTs&&... arrays) { using AHList = brigand::list::type...>; VTKM_STATIC_ASSERT_MSG(sizeof...(ArrayTs) > 0, "Must specify at least one source array handle for " "ArrayHandleDecorator. Consider using " "ArrayHandleImplicit instead."); VTKM_STATIC_ASSERT_MSG(internal::decor::AllAreArrayHandles::value, "Trailing template parameters for " "ArrayHandleDecorator must be a list of ArrayHandle " "types."); return { numValues, std::forward(f), std::forward(arrays)... }; } } } // namespace vtkm::cont #ifdef VTKM_USE_TAO_SEQ #undef VTKM_USE_TAO_SEQ #endif #endif //vtk_m_ArrayHandleDecorator_h