5.3. Function Interface Objects

For flexibility’s sake, a worklet is free to declare a ControlSignature with whatever number of arguments are sensible for its operation. The viskores::cont::Invoker is expected to support arguments that match these arguments, and part of the invocation operation may require these arguments to be augmented before the worklet is scheduled. This leaves the invoker with the tricky task of managing some collection of arguments of unknown size and unknown types.

To simplify this management, Viskores has the viskores::internal::FunctionInterface class. viskores::internal::FunctionInterface is a templated class that manages a generic set of arguments and the return value from a function. An instance of viskores::internal::FunctionInterface holds an instance of each argument. You can apply the arguments in a viskores::internal::FunctionInterface object to a functor with a compatible prototype, and the resulting value of the function call is saved in the viskores::internal::FunctionInterface.

template<typename FunctionSignature>
class FunctionInterface

Holds parameters and result of a function.

To make Viskores easier for the end user developer, the Invoke method of dispatchers takes an arbitrary amount of arguments that get transformed and swizzled into arguments and return value for a worklet operator. In between these two invocations a complicated series of transformations and operations can occur.

Supporting arbitrary function and template arguments is difficult and really requires separate implementations for pre-C++11 and C++11 versions of compilers. Thus, variadic template arguments are, at this point in time, something to be avoided when possible. The intention of FunctionInterface is to collect most of the variadic template code into one place. The FunctionInterface template class takes a function signature, which can have a variable number of arguments. The FunctionInterface will hold in its state a copy of all input parameters (regardless of number or type) and the return value if it exists (i.e. non-nullptr) and the function has been invoked. This means that all arguments can be passed around in a single object so that objects and functions dealing with these variadic parameters can be templated on a single type (the type of FunctionInterface).

Note that the indexing of the parameters in a FunctionInterface starts at 1. You can think of the return value being the parameter at index 0, even if there is no return value. Although this is uncommon in C++, it matches better the parameter indexing for other classes that deal with function signatures.

The FunctionInterface contains several ways to invoke a functor whose parameters match those of the function interface. This allows you to complete the transition of calling an arbitrary function (like a worklet).

The following is a rundown of how a FunctionInterface is created and used. See the independent documentation for more details.

Use the make_FunctionInterface function to create a FunctionInterface and initialize the state of all the parameters. make_FunctionInterface takes a variable number of arguments, one for each parameter. Since the return type is not specified as an argument, you must always specify it as a template parameter.

viskores::internal::FunctionInterface<void(viskores::IdComponent,double,char)> functionInterface =
    viskores::internal::make_FunctionInterface<void>(1, 2.5, 'a');

The number of parameters can be retrieved either with the constant field ARITY or with the GetArity method.

functionInterface.GetArity();

You can get a particular parameter using the templated function ParameterGet. The template parameter is the index of the parameter (starting at 1).

Finally, there is a way to replace all of the parameters at once. The StaticTransform methods take a transform functor that modifies each of the parameters. See the documentation for this method for details on how it is used.

Public Functions

inline viskores::IdComponent GetArity() const

Returns the number of parameters held in this FunctionInterface.

The return value is the same as ARITY.

template<typename Transform>
inline StaticTransformType<Transform>::type StaticTransformCont(const Transform &transform)

Transforms the FunctionInterface based on compile-time information.

The StaticTransform methods transform all the parameters of this FunctionInterface to different types and values based on compile-time information. It operates by accepting a functor that two arguments. The first argument is the parameter to transform and the second argument is an IndexTag specifying the index of the parameter (which can be ignored in many cases). The functor’s return value is the transformed value. The functor must also contain a templated struct name ReturnType with an internal type named type that defines the return type of the transform for a given input type and parameter index.

The transformation is only applied to the parameters of the function. The return argument is unaffected.

The return type can be determined with the StaticTransformType template.

Here is an example of a transformation that converts a FunctionInterface to another FunctionInterface containing pointers to all of the parameters.

struct MyTransformFunctor {
  template<typename T, viskores::IdComponent Index>
  struct ReturnType {
    typedef const T *type;
  };

  template<typename T, viskores::IdComponent Index>
  VISKORES_CONT
  const T *operator()(const T &x, viskores::internal::IndexTag<Index>) const {
    return &x;
  }
};

template<typename FunctionSignature>
typename viskores::internal::FunctionInterface<FunctionSignature>::template StaticTransformType<MyTransformFunctor>::type
ImportantStuff(const viskores::internal::FunctionInterface<FunctionSignature> &funcInterface)
{
  return funcInterface.StaticTransformCont(MyTransformFunctor());
}

Public Static Attributes

static constexpr viskores::IdComponent ARITY = SigInfo::Arity

The number of parameters in this Function Interface.

template<viskores::IdComponent ParameterIndex>
struct ParameterType
template<typename Transform>
struct StaticTransformType

5.3.1. Declaring and Creating

viskores::internal::FunctionInterface is a templated class with a single parameter. The parameter is the function signature of the function. A signature is a function type. The syntax in C++ is the return type followed by the argument types enclosed in parentheses.

1  // FunctionInterfaces matching some common POSIX functions.
2  viskores::internal::FunctionInterface<size_t(const char*)> strlenInterface;
3
4  viskores::internal::FunctionInterface<char*(char*, const char* s2, size_t)>
5    strncpyInterface;

The viskores::internal::make_FunctionInterface() function provides an easy way to create a viskores::internal::FunctionInterface and initialize the state of all the parameters. viskores::internal::make_FunctionInterface() takes a variable number of arguments, one for each parameter. Since the return type is not specified as an argument, you must always specify it as a template parameter.

template<typename R, typename ...Args>
FunctionInterface<R(Args...)> viskores::internal::make_FunctionInterface(const Args&... args)

Create a FunctionInterface.

make_FunctionInterface is a function that takes a variable number of arguments and returns a FunctionInterface object containing these objects. Since the return type for the function signature is not specified, you must always specify it as a template parameter

viskores::internal::FunctionInterface<void(int,double,char)> functionInterface =
    viskores::internal::make_FunctionInterface<void>(1, 2.5, 'a');
1  const char* s = "Hello World";
2  static const size_t BUFFER_SIZE = 100;
3  char* buffer = (char*)malloc(BUFFER_SIZE);
4
5  strlenInterface = viskores::internal::make_FunctionInterface<size_t>(s);
6
7  strncpyInterface =
8    viskores::internal::make_FunctionInterface<char*>(buffer, s, BUFFER_SIZE);

5.3.2. Parameters

Once created, viskores::internal::FunctionInterface contains methods to query and manage the parameters and objects associated with them. The number of parameters can be retrieved either with the constant field viskores::internal::FunctionInterface::ARITY or with the viskores::internal::FunctionInterface::GetArity() method.

Example 5.13 Getting the arity of a viskores::internal::FunctionInterface.
1  VISKORES_STATIC_ASSERT(
2    viskores::internal::FunctionInterface<size_t(const char*)>::ARITY == 1);
3
4  viskores::IdComponent arity = strncpyInterface.GetArity(); // arity = 3

You can use the viskores::internal::ParameterGet() function to retrieve a parameter from a viskores::internal::FunctionInterface. When using viskores::internal::ParameterGet(), you have to specify the index of the parameter using a template argument. Note that the parameters in viskores::internal::FunctionInterface start at index 1. Although this is uncommon in C++, it is customary to number function arguments starting at 1.

template<viskores::IdComponent ParameterIndex, typename FunctionSignature>
auto viskores::internal::ParameterGet(const FunctionInterface<FunctionSignature> &fInterface) -> decltype(detail::ParameterGet(fInterface.Parameters, viskores::internal::IndexTag<ParameterIndex>{}))

Gets the value for the parameter of the given index.

Parameters are indexed starting at 1. To use this method you have to specify a static, compile time index.

template<FunctionSignature>
void Foo(const viskores::internal::FunctionInterface<FunctionSignature> &fInterface)
{
  bar( ParameterGet<2>(fInterface) );
}
1template<typename FunctionSignature>
2void GetFirstParameter(
3  const viskores::internal::FunctionInterface<FunctionSignature>& interface)
4{
5  // The following two uses of GetParameter are equivalent
6  std::cout << viskores::internal::ParameterGet<1>(interface) << std::endl;
7}

5.3.3. Transformations

Rather than replace a single item in a viskores::internal::FunctionInterface, it is desirable to change them all in a similar way. viskores::internal::FunctionInterface supports a static transform that replaces all of the arguments with new types defined at compile time.

The static transform method, viskores::internal::FunctionInterface::StaticTransformCont(), operates by accepting a functor that defines a function with two arguments. The first argument is the viskores::internal::FunctionInterface parameter to transform. The second argument is an instance of the viskores::internal::IndexTag templated class that statically identifies the parameter index being transformed. An viskores::internal::IndexTag object has no state, but the class contains a static integer named viskores::internal::IndexTag::INDEX. The function returns the transformed argument.

template<viskores::IdComponent Index>
struct IndexTag

A convenience tag to represent static indices.

Some classes like FunctionInterface have a list of items that have numeric indices that must be resolved at compile time. Typically these are referenced with an integer template argument. However, such template arguments have to be explicitly defined in the template. They cannot be resolved through function or method arguments. In such cases, it is convenient to use this tag to encapsulate the index.

The functor must also contain a templated class named ReturnType with an internal type named type that defines the return type of the transform for a given parameter type. ReturnType must have two template parameters. The first template parameter is the type of the viskores::internal::FunctionInterface parameter to transform. It is the same type as passed to the operator. The second template parameter is a viskores::IdComponent specifying the index.

The transformation is only applied to the parameters of the function. The return argument is unaffected.

The return type can be determined with the viskores::internal::FunctionInterface::StaticTransformType template in the viskores::internal::FunctionInterface class. viskores::internal::FunctionInterface::StaticTransformType has a single parameter that is the transform functor and contains a type named type that is the transformed viskores::internal::FunctionInterface.

In the following example, a static transform is used to convert a viskores::internal::FunctionInterface to a new object that has the pointers to the parameters rather than the values themselves. The parameter index is always ignored because all parameters are uniformly transformed.

Example 5.15 Using a static transform of the function interface class.
 1struct ParametersToPointersFunctor
 2{
 3  template<typename T, viskores::IdComponent Index>
 4  struct ReturnType
 5  {
 6    using type = const T*;
 7  };
 8
 9  template<typename T, viskores::IdComponent Index>
10  VISKORES_CONT const T* operator()(const T& x,
11                                    viskores::internal::IndexTag<Index>) const
12  {
13    return &x;
14  }
15};
16
17template<typename FunctionInterfaceType>
18VISKORES_CONT typename FunctionInterfaceType::template StaticTransformType<
19  ParametersToPointersFunctor>::type
20ParametersToPointers(FunctionInterfaceType& functionInterface)
21{
22  return functionInterface.StaticTransformCont(ParametersToPointersFunctor());
23}