Home > front end >  Pointer level return type based on template function arguments
Pointer level return type based on template function arguments

Time:04-10

For a tensor class I would like to have a template creating functions like

double* mat(int size1);
double** mat(int size1, int size2);
double*** mat(int size1, int size2, int size3);

i.e. the pointer level of the return type depends on the number of inputs. The base case would just be

double* mat(int size1){
  return new double[size1];
}

I thought a variadic template could somehow do the trick

template<typename T, typename... size_args>
T* mat(int size1, size_args... sizes) {
  T* m = new T[size1];
  for(int j = 0; j < size1; j  ){
    m[j] = mat(sizes...);
  }
  return m;
}

Deducing the template parameter in m[j] = mat(sizes...); doesn't seem to work though when more than one recursive call would be necessary, as in

auto p = mat<double**>(2,3,4);

but how could I provide the parameter in the recursive call? The correct parameter would be one pointer level below T, that is mat<double**> for T=double***. I feel like I miss something important about templates here.

CodePudding user response:

you cannot declare m and return type as T* since it is not in multiple dimension.

template<typename T, typename size_type>
auto mat(size_type size){return new T[size];}

template<typename T, typename size_type, typename... size_types>
auto mat(size_type size, size_types... sizes){
    using inner_type = decltype(mat<T>(sizes...));
    inner_type* m = new inner_type[size];
    for(int j = 0; j < size; j  ){
        m[j] = mat<T>(sizes...);
    }
    return m;
}

CodePudding user response:

For lack of a better name, I'm going to call the template meta-function that creates a pointer type with the desired "depth" and type, a "recursive pointer". Such a thing can be implemented like so:

template <size_t N, class T>
struct RecursivePtr
{
    using type = RecursivePtr<N-1, T>::type*;
};

template <class T>
struct RecursivePtr<0, T>
{
    using type = T;
};

template <size_t N, class T>
using recursive_ptr_t = RecursivePtr<N, T>::type; 

recursive_ptr_t<4, int> for example creates int****. So in your case you can go ahead and implement the mat function as:

template <class... Args>
auto mat(Args... args)
{
    recursive_ptr_t<sizeof...(Args), double> m;
    
    // Runtime allocate your Npointer here.
    
    return m;
}

Demo

Some ideas to give added type safety to the mat function are:

  1. Add static asserts that all provided types are sizes
  2. Statically assert that at least one size has been provided

Minor note, when I say runtime allocate your Npointer above, I mean something like:

template <class T, class... Sz>
void alloc_ar(T* &ar, size_t s1, Sz... ss)
{
    if constexpr (sizeof...(Sz))
    {
        ar = new T[s1];
        for (size_t i(0); i < s1; i  )
            alloc_ar(ar[i], ss...);
    }
    else
    {
        ar = new T[s1];
    }
}

Made a Demo where I show the allocation, but not the deallocation.

A reasonable alternative to this is to allocate one contiguous chunk of memory (sized == multiplicate of dimensions) and use the multidimensional pointer to the beginning of that chunk for syntactic sugar when accessing it. This also provides an easier way to deallocate memory.

A second alternative is to use nested vector of vectors (of vectors of vectors...) with the same generation mechanics as the Npointer. This eliminates the need for manual memory management and would probably force you to wrap the whole thing in a more expressive class:

template <class... Dims>
class mat
{
  template <size_t N, class T>
  struct RecursivePtr
  {
    using type = std::vector<RecursivePtr<N-1, T>::type>;
  };

  template <class T>
  struct RecursivePtr<0, T>
  {
    using type = T;
  };

  // This is the replacement to double***
  // translates to vector<vector<vector<double>>>
  RecursivePtr<N, T>::type _data; 

public:
  // construction, argument deduction etc ... 
};
  • Related