Flattening Template Parameters with Tags

From Daxtoolkit
Jump to: navigation, search

At least three people (specifically Robert, Utkarsh, and Tim) have suggested the use of tags (such as those in thrust) to implement the template polymorphic behavior of classes in Dax. I have previously ignored the suggestions, figuring that it was easier to place structures in the template arguments than to have empty tag structures that are then used on separated templated implementations.

My current work implementing ArrayHandle Part Deux has made me rethink this position. This document is an augmentation on the design in ArrayHandle Part Deux to flatten and simplify the templating through tags.


The problem with the design in ArrayHandle Part Deux is that classes contain templates of class and these class are used in other templates. Many of these templates require template template parameters (not a mistake, that is what they are called). You end up with some pretty messing template declarations (although well hidden within the Dax code). For example, here is the template declaration for a function that loads an array to a field.

  template<template <typename, class> class FieldType,
           typename ValueType,
           template <typename> class Container,
           class DeviceAdapter,
           class GridType>
  FieldType<ValueType, typename DeviceAdapter::template ExecutionAdapter<Container> >
      const dax::cont::ArrayHandle<ValueType, Container, DeviceAdapter>
      const GridType &grid)

This is a mess. And incidentally, it is not working. Either the compiler or I is not understanding this code correctly.

Basic Design

The basic design of the new system is to create tags that represent the ArrayContainerControl and DeviceAdapter. The actual classes and functions are templates based on this tag. The point is that you can template other classes just on the tags and still be able to retrieve structures. You don't get things like template template parameters and nested templates.


The ArrayContainerControl is a class that manages the allocation of an array. We start with this template class prototype, which is never defined in this general setting. This is probably declared in the dax::cont::internal namespace.

template <typename T, class tag> class ArrayContainerControl;

Now, to create an implementation of an ArrayContainerControl, for example the basic version, you declare a tag in the dax::cont namespace such as this.

struct ArrayContainerControlTagBasic {  };

Now in the internal namespace, you provide a partial speculation of ArrayContainerControl that is actually implemented.

template<typename T>
class ArrayContainerControl<T, ArrayContainerControl>
  typedef T *IteratorType;
  typedef const T *IteratorConstType;
  ArrayContainerControl(IteratorConstType, IteratorConstType) {  }


The DeviceAdapter implementation will be similar to that of ArrayContainerControl. Once again in the dax::cont namespace you define a tag.

struct DeviceAdapterTagSerial {  };

Unlike the previous class, the DeviceAdapter generally does not have any state. Instead, it has several static methods, which can now just be implemented as functions (that are overloaded for the different tags). For example,

template<typename T, class ArrayContainerControlTag>
void Sort(ArrayHandle<T, ArrayContainerControlTag, DeviceAdapterTagSerial> &) {  }

The original DeviceAdapter also had an internal templated class called ArrayManagerExecution. This is easily implemented as its own class templates on the DeviceAdapterTag.

template<typename T, class ArrayContainerControlTag, class DeviceAdapterTag>
class ArrayManagerExecution;

template<typename T, class ArrayContainerControlTag>
class ArrayManagerExecution<T, ArrayContainerControlTag, DeviceAdapterSerialTag> {  };


The ArrayHandle class does not change much although the template parameters are simplified by not having template template parameters. Instead, it just uses the tags.

template<typename T,
         class ArrayContainerControlTag = DEFAULT_ARRAY_CONTAINER_CONTROL,
         class DeviceAdapterTag = DEFAULT_DEVICE_ADAPTER>
class ArrayHandle {  };


The ExecutionAdapter is a bit tricky for a couple of reasons. First, we want to make sure we have one class that contains everything necessary to run. In principle we need two tags, one for the container and one for the device adapter, both of which may modify the behavior. But it is too onerous to make worklet designers specify both.

The second tricky part is that we currently have a unidirectional dependency between the "packages" of Dax. cont depends on (and can access) exec, but not the other way around. Thus, we don't want to have to include any cont header files from any exec header files.

For these reasons, I propose leaving well enough alone for the ExecutionAdapter. Apart from being defined as a class templates on the container and adapter tags instead of a templated internal class of the adapter, the structure remains the same.

I'm not entirely sure what namespace this should go in. The ExecutionAdapter is used in the execution environment but defined in the control package. I'm thinking perhaps it does not matter. It could just be hidden in the implementation of Schedule. --Kenneth Moreland 23:38, 19 May 2012 (EDT)

No, scratch that. The worklet-executing functions need to the ExecutionAdapter to instantiate execution environment objects. I already have a fake doxygen class in dax::exec, so I'll put it there. --Kenneth Moreland 12:21, 21 May 2012 (EDT)


Sandia National Laboratories is a multi-program laboratory managed and operated by Sandia Corporation, a wholly owned subsidiary of Lockheed Martin Corporation, for the U.S. Department of Energy's National Nuclear Security Administration under contract DE-AC04-94AL85000.

SandiaLogo.png DOELogo.png

SAND 2012-5993P