# Add a vtkm::Tuple class This change added a `vtkm::Tuple` class that is very similar in nature to `std::tuple`. This should replace our use of tao tuple. The motivation for this change was some recent attempts at removing objects like `Invocation` and `FunctionInterface`. I expected these changes to speed up the build, but in fact they ended up slowing down the build. I believe the problem was that these required packing variable parameters into a tuple. I was using the tao `tuple` class, but it seemed to slow down the compile. (That is, compiling tao's `tuple` seemed much slower than compiling the equivalent `FunctionInterface` class.) The implementation of `vtkm::Tuple` is using `pyexpander` to build lots of simple template cases for the object (with a backup implementation for even longer argument lists). I believe the compiler is better and parsing through thousands of lines of simple templates than to employ clever MPL to build general templates. ## Usage The `vtkm::Tuple` class is defined in the `vtkm::Tuple.h` header file. A `Tuple` is designed to behave much like a `std::tuple` with some minor syntax differences to fit VTK-m coding standards. A tuple is declared with a list of template argument types. ``` cpp vtkm::Tuple> myTuple; ``` If given no arguments, a `vtkm::Tuple` will default-construct its contained objects. A `vtkm::Tuple` can also be constructed with the initial values of all contained objects. ``` cpp vtkm::Tuple> myTuple(0, vtkm::Vec3f(0, 1, 2), array); ``` For convenience there is a `vtkm::MakeTuple` function that takes arguments and packs them into a `Tuple` of the appropriate type. (There is also a `vtkm::make_tuple` alias to the function to match the `std` version.) ``` cpp auto myTuple = vtkm::MakeTuple(0, vtkm::Vec3f(0, 1, 2), array); ``` Data is retrieved from a `Tuple` by using the `vtkm::Get` method. The `Get` method is templated on the index to get the value from. The index is of type `vtkm::IdComponent`. (There is also a `vtkm::get` that uses a `std::size_t` as the index type as an alias to the function to match the `std` version.) ``` cpp vtkm::Id a = vtkm::Get<0>(myTuple); vtkm::Vec3f b = vtkm::Get<1>(myTuple); vtkm::cont::ArrayHandle c = vtkm::Get<2>(myTuple); ``` Likewise `vtkm::TupleSize` and `vtkm::TupleElement` (and their aliases `vtkm::Tuple_size`, `vtkm::tuple_element`, and `vtkm::tuple_element_t`) are provided. ## Extended Functionality The `vtkm::Tuple` class contains some functionality beyond that of `std::tuple` to cover some common use cases in VTK-m that are tricky to implement. In particular, these methods allow you to use a `Tuple` as you would commonly use parameter packs. This allows you to stash parameter packs in a `Tuple` and then get them back out again. ### For Each `vtkm::Tuple::ForEach()` is a method that takes a function or functor and calls it for each of the items in the tuple. Nothing is returned from `ForEach` and any return value from the function is ignored. `ForEach` can be used to check the validity of each item. ``` cpp void CheckPositive(vtkm::Float64 x) { if (x < 0) { throw vtkm::cont::ErrorBadValue("Values need to be positive."); } } // ... vtkm::Tuple tuple( CreateValue1(), CreateValue2(), CreateValue3()); // Will throw an error if any of the values are negative. tuple.ForEach(CheckPositive); ``` `ForEach` can also be used to aggregate values. ``` cpp struct SumFunctor { vtkm::Float64 Sum = 0; template void operator()(const T& x) { this->Sum = this->Sum + static_cast(x); } }; // ... vtkm::Tuple tuple( CreateValue1(), CreateValue2(), CreateValue3()); SumFunctor sum; tuple.ForEach(sum); vtkm::Float64 average = sum.Sum / 3; ``` ### Transform `vtkm::Tuple::Transform` is a method that builds a new `Tuple` by calling a function or functor on each of the items. The return value is placed in the corresponding part of the resulting `Tuple`, and the type is automatically created from the return type of the function. ``` cpp struct GetReadPortalFunctor { template typename Array::ReadPortal operator()(const Array& array) const { VTKM_IS_ARRAY_HANDLE(Array); return array.ReadPortal(); } }; // ... auto arrayTuple = vtkm::MakeTuple(array1, array2, array3); auto portalTuple = arrayTuple.Transform(GetReadPortalFunctor{}); ``` ### Apply `vtkm::Tuple::Apply` is a method that calls a function or functor using the objects in the `Tuple` as the arguments. If the function returns a value, that value is returned from `Apply`. ``` cpp struct AddArraysFunctor { template vtkm::Id operator()(Array1 inArray1, Array2 inArray2, Array3 outArray) const { VTKM_IS_ARRAY_HANDLE(Array1); VTKM_IS_ARRAY_HANDLE(Array2); VTKM_IS_ARRAY_HANDLE(Array3); vtkm::Id length = inArray1.GetNumberOfValues(); VTKM_ASSERT(inArray2.GetNumberOfValues() == length); outArray.Allocate(length); auto inPortal1 = inArray1.ReadPortal(); auto inPortal2 = inArray2.ReadPortal(); auto outPortal = outArray.WritePortal(); for (vtkm::Id index = 0; index < length; ++index) { outPortal.Set(index, inPortal1.Get(index) + inPortal2.Get(index)); } return length; } }; // ... auto arrayTuple = vtkm::MakeTuple(array1, array2, array3); vtkm::Id arrayLength = arrayTuple.Apply(AddArraysFunctor{}); ``` If additional arguments are given to `Apply`, they are also passed to the function (before the objects in the `Tuple`). This is helpful for passing state to the function. (This feature is not available in either `ForEach` or `Transform` for technical implementation reasons.) ``` cpp struct ScanArrayLengthFunctor { template std::array operator()(const std::array& partialResult, const Array& nextArray, const Remaining&... remainingArrays) const { std::array nextResult; std::copy(partialResult.begin(), partialResult.end(), nextResult.begin()); nextResult[N] = nextResult[N - 1] + nextArray.GetNumberOfValues(); return (*this)(nextResult, remainingArray); } template std::array operator()(const std::array& result) const { return result; } }; // ... auto arrayTuple = vtkm::MakeTuple(array1, array2, array3); std::array = arrayTuple.Apply(ScanArrayLengthFunctor{}, std::array{ 0 }); ```