Korijn
Korijn

Reputation: 1403

How to represent a set of values of arbitrary length and type combination?

I'm writing code to analyze n-dimensional datasets. I wrote a class that represents an axis of an arbitrary dataset. Every axis can have a different datatype, so I chose to use template programming.

class BaseAxis
{
};

template <class T>
class Axis : public BaseAxis
{
public:
    Axis(const T &begin, const T &end);

    const T begin;
    const T end;
};

This allows me to create new Axis objects of arbitrary types. A user can configure a more high level class NTree which represents the entire dataset by adding the correct axes, as follows:

ntree = new ntree::NTree<float>();
ntree->AddAxis<int>(0, 8);
ntree->AddAxis<float>(-100.0f, 200.0f);

This would create a 2 dimensional floating point dataset, with an integer axis spanning from 0 to 8 and a floating point axis spanning from -100.0f to 200.0f. To get a value from the dataset you should be able to query the datastructure as follows:

ntree->Query(2, 2.5f);

I'm currently trying to figure out how to write such a function (or an object that can represent these parameters), since they can be of arbitrary amount and type. I've read about boost::variant but I'm unsure how to apply this in this case as I'm not experienced enough in c++ templates and libraries.

Here's the relevant part of code of the NTree class.

template <class T>
class NTree
{
public:
    NTree();
    ~NTree();

    template <class A>
    void AddAxis(const A &start, const A &end);

protected:
    std::vector<BaseAxis*> *axes;
};

template <class T>
template <class A>
void NTree<T>::AddAxis(const A &start, const A &end)
{
    BaseAxis* newAxis = new Axis<A>(start, end);
    this->axes->push_back(newAxis);
}

Therefore my question is as follows: how to represent a set of values of arbitrary length and type combination?

Upvotes: 2

Views: 364

Answers (1)

Thibaut
Thibaut

Reputation: 2420

You can define your container using a Typelist or variadic templates. This way you could preserve strong type safety without using opaque containers. You can add and remote axis from the container, probably at the cost of copying the buffers over.

A clean implementation for that could use dedicated allocators for each type and a proxy class to aggregate them. You can then have a templated accessor in your proxy class which would cast the allocators back using the parameters and call their acess function.

This is a (very truncated) implementation of an N-dimensional container. In this case, a volume is defined by a list of arrays of same dimensions but different types, so not exaclty what you need, but close:

 // opaque base class for allocator
 class UnsafeAllocator{...};

 // specialized allocator:
 template<class T>
 class Allocator : public UnsafeAllocator {
      ...
      virtual T& operator[] (const size_t * position) { ... }
 };

 // proxy class for the container:
 template<class TL>
 class Volume {
 protected:
     // each field has its own allocator, allocated in the constructor
     UnsafeAllocator *opaque_allocator[Length<TL>::value];
 public:
     // put a constuctor with the current dimensions here and fill in the array of allocators using the typelist

     // typesafe getter
     template <int index> 
     typename TypeAt<TL, index>::Result &
     get(const std::initializer_list<size_t> &position){
           typedef typename TypeAt<TL, index>::Result FieldType;
           typedef Allocator<FieldType> SafeAllocator;
           SafeAllocator* allocator = dynamic_cast<SafeAllocator *>(opaque_allocator[index]);
           assert(allocator != NULL);
           return (*allocator)[position];
      }
  };

  // usage would be:
  typedef MakeTypeList(float, int, double) types;
  // define a two dimensional volume for each type
  Volume<types> volume({1024,1024});
  // example of typesafe access for the first type at position 0,0:
  float f = volume.get<0>({0,0});

The main drawback of this implementation is you need to know all the typelists at compile time. I don't know if that is part of your specifications or not.

If it is not, i don't think there is any way to have a container with a variable number of variadic types without using opaque proxys everywhere, which would kill the readability (and possibly the safety) of your code.

Upvotes: 1

Related Questions