Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++11 variadic programming, how to define a tower of vectors

How (if possible) can I use c++11 variadic programming to define a series of vector's in a function body, (or in other words, a sequence of N-dimensional arrays with decreasing N's until 0), like the variables below?

vector<vector<vector<int>>> v<3>;
vector<vector<int>> v<2>;
vector<int> v<1>;
int v<0>;

What I imagined is something like:

#include <iostream>
#include <vector>
using namespace std;

template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

template<int ...S>
void f(seq<S...>) {
  //how do I write definitions of v<N> here?
  vector<vector<...(N layers)<vector<int> ...> v<N>;     //??how-to, not valid c++
  vector<vector<...(N -1 layers)<vector<int> ...> v<N-1>;//??how-to, not valid c++
  //...
  vector<int> v<1>;  //??how-to, not valid c++
  int v<0>;          //??how-to, not valid c++

  //...
}

int main() {
  f(typename gens<3>);
  return 0;
}

Also, would this be easier in c++14?

Thanks,

--EDIT--

Just to clarify, what I mean by "a tower of vectors" is best described by a N-tuple (v_1, v_2, ..., v_N), where N is an integral template parameter. v_1 is of vector, v_2 is of vector>, and so on.

--EDIT2--

So far, quantdev and R's answers have successfully solved the problem of defining a N-tuple for any fixed N, like 3, but cannot generate a tuple for an unspecified N. In addition to the functionality in the answers, I need a function which can be used like gen_tower<N> that returns tuple(v1,v2,...,vN).

Consider the example of using variadic programming to compute factorials. I need a function to compute factorial factorial<N>() for any N, in addition to the ability to write out any specific expression <1*2*3> manually. (That was the reason why I asked about variadic programming and whether c++14 will make it easier.)

P.S.

Purely out of personal interests, I want this sequence to hopefully implement a generic function that can read a N-dimensional array from file. I don't know exactly how yet, but I think at step one, I should be able to define the final N-dimensional array, and the intermediate k-dimensional arrays for k from N-1 to 1. I can read 2-dimensional arrays, and 3-dimensionals. But it would be nice to be able to read arrays of any dimensions.

like image 366
thor Avatar asked Jun 28 '14 03:06

thor


3 Answers

There is no need for variadics, a recursive typedef is sufficient to generate those types at compile time.


How is is implemented ?

1) Provide a template with 2 arguments : the vector elements type (T), and the desired dimension of the structure (size_t N). Declare a typedef type : it will be based on the declaration of type for the template instantiated with the depth N-1, hence the recursion.

template<typename T, size_t N>
struct VectorGenerator
{
    typedef std::vector< typename VectorGenerator<T, N-1>::type > type;
};

2) Provide a termination case for ending the recursion, here a specialization of our template with the dimension 0, declaring the type of a usual std::vector<T>.

template<typename T>
struct VectorGenerator<T, 0>
{
    typedef std::vector<T> type;
};

How to use it ?

We can now declare a vector v of type VectorGenerator<T, N>::type :

VectorGenerator<double, 4>::type v; // v as a depth of 4 and handle double

But it's not very readable or convenient, and pretty verbose. Let's introduce new names for our types.

This is the perfect case for template aliasing, with the (C++11) using keyword for aliasing. We have 2 different ways of aliasing :

1) Declare an alias for a particular dimension and type, here we call it V3 for N=3 and T=double :

using V3 = VectorGenerator<double, 3>::type;  // Alias

V3 v;                                         // Use the alias

Or,

2) Declare a template alias for a particular type, leaving the dimension as a template parameter:

template <size_t N> 
using V = typename VectorGenerator<double, N>::type;  // Alias

V<3> v;                                              // Use the Alias

Final code sample:

template<typename T, size_t N>
struct VectorGenerator
{
    typedef std::vector< typename VectorGenerator<T, N-1>::type > type;
};

template<typename T>
struct VectorGenerator<T, 0>
{
    typedef std::vector<T> type;
};

// Alias for V3, V2 ... usage
using V3 = VectorGenerator<double, 3>::type;
using V2 = VectorGenerator<double, 2>::type;

// Alias for V <k> usage
template <size_t N> 
using V = typename VectorGenerator<double, N>::type;

int main() {

    V<3> v3;
    V<2> v2;
    v3.push_back(v2);
    return 0;
}

Notes :

  • Consider the Boost MultiDimensional Array library.
  • I am not sure of what your final goal is, but this might well be an overkill.
  • As for your second edit, declaring a tuple with multiple vectors of different dimensions is now easy :

Example:

auto tower = std::tuple<V<1>, V<2>, V<3>>(v1, v2, v3);

For the generic tuple generation of multiple "towers", @mpark gave a working C++14 solution, I adapt it here to my code sample:

template <typename T>
struct identity { using type = T; };

// Generate a tuple of towers by mapping index_sequence over gen_tower.
template <typename T, std::size_t... Is>
std::tuple<VectorGenerator<T, Is>...> gen_towers_impl(std::integer_sequence<Is...>);

// Make an index_sequence for N and use gen_towers_impl.
template <typename T, std::size_t N>
struct gen_towers
    : identity<decltype(gen_towers_impl<T>(std::make_index_sequence<N>()))> {};

// Convenience type aliases
template <typename T, std::size_t N>
using gen_towers_t = typename gen_towers<T, N>::type;

You will need -std=c++1y to compile it (and include <utility> and <tuple> headers)

See a working example here.

like image 65
quantdev Avatar answered Oct 06 '22 09:10

quantdev


You can find a similar question but dealing with std::map at shorthand syntax for c++ map in map.

Here's one for std::vector.

#include <iostream>
#include <vector>

template<int N, typename V>
struct NVector { typedef std::vector<typename NVector<N-1, V>::type> type; };

template<typename V>
struct NVector<1, V> { typedef std::vector<V> type; };

int main(int argc, const char *argv[]) {

   NVector<1, int>::type v1(10, 0);

   NVector<2, int>::type v2;
   v2.push_back(v1);

   NVector<3, int>::type v3;
   v3.push_back(v2);

   for ( int i = 0; i < 10; ++i )
   {
      std::cout << v3[0][0][i] << " ";
   }
   std::cout << std::endl;
}

Output:

0 0 0 0 0 0 0 0 0 0 

Update

You can simplify the usage of these vectors with a using declaration.

#include <iostream>
#include <vector>

template<int N, typename V>
struct NVector { typedef std::vector<typename NVector<N-1, V>::type> type; };

template<typename V>
struct NVector<1, V> { typedef std::vector<V> type; };

template<int N, typename Val>
using V = typename NVector<N, Val>::type;

int main(int argc, const char *argv[]) {

   V<1, int> v1(10, 0);

   V<2, int> v2;
   v2.push_back(v1);

   V<3, int> v3;
   v3.push_back(v2);

   for ( int i = 0; i < 10; ++i )
   {
      std::cout << v3[0][0][i] << " ";
   }
   std::cout << std::endl;
}

If you want to make it still simplified by assuming the value type to be int, you can use:

#include <iostream>
#include <vector>

template<int N, typename V>
struct NVector { typedef std::vector<typename NVector<N-1, V>::type> type; };

template<typename V>
struct NVector<1, V> { typedef std::vector<V> type; };

template<int N>
using V = typename NVector<N, int>::type;

int main(int argc, const char *argv[]) {

   V<1> v1(10, 0);

   V<2> v2;
   v2.push_back(v1);

   V<3> v3;
   v3.push_back(v2);

   for ( int i = 0; i < 10; ++i )
   {
      std::cout << v3[0][0][i] << " ";
   }
   std::cout << std::endl;
}
like image 27
R Sahu Avatar answered Oct 06 '22 10:10

R Sahu


I won't go into too much detail in generating a single tower since it's explained by other answers here. Here is my version of gen_tower<T, I> which generates a tower of vectors depth I.

For example, gen_tower_t<int, 2> is std::vector<std::vector<T>>.

// Useful for defining meta-functions compactly.
template <typename T>
struct identity { using type = T; };

gen_tower<T, I>

// Forward declaration.
template <typename T, std::size_t I>
struct gen_tower;

// Convenience type alias.
template <typename T, std::size_t I>
using gen_tower_t = typename gen_tower<T, I>::type;

// Base case.
template <typename T>
struct gen_tower<T, 0> : identity<T> {};

// Wrap std::vector around tower of depth I - 1.
template <typename T, std::size_t I>
struct gen_tower : identity<std::vector<gen_tower_t<T, I - 1>>> {};

gen_towers<T, N>

Now we can use std::index_sequence to define N towers.

// Generate a tuple of towers by mapping index_sequence over gen_tower.
template <typename T, std::size_t... Is>
std::tuple<gen_tower_t<T, Is>...> gen_towers_impl(std::index_sequence<Is...>);

// Make an index_sequence for N and use gen_towers_impl.
template <typename T, std::size_t N>
struct gen_towers
    : identity<
          decltype(gen_towers_impl<T>(std::make_index_sequence<N>()))> {};

// Convenience type aliases
template <typename T, std::size_t N>
using gen_towers_t = typename gen_towers<T, N>::type;

Examples

static_assert(std::is_same<gen_tower_t<int, 0>, int>::value, "");

static_assert(std::is_same<
                  gen_tower_t<int, 2>,
                  std::vector<std::vector<int>>>::value, "");

static_assert(std::is_same<
                  gen_towers_t<int, 2>,
                  std::tuple<int, std::vector<int>>>::value, "");

static_assert(std::is_same<
                  gen_towers_t<int, 3>,
                  std::tuple<int,
                             std::vector<int>,
                             std::vector<std::vector<int>>>>::value, "");

int main() {}
like image 23
mpark Avatar answered Oct 06 '22 09:10

mpark