3

Is there a C++11 implementation/library publicly available that offers a container of tuples where each tuple element is stored in its own flat container?

What I am looking for is something like this

struct A { using type = double; };
struct B { using type = int; };
struct C { using type = int; };

std::size_t num_elements = 6;

flat_tuple_container<A, B, C> c(num_elements);

where internally the allocated memory is contiguous for each of A, B, and C's types, giving me a container that would internally like the following if I wrote it manually:

struct manual_cont {
  std::vector<double> v1;
  std::vector<int> v2;
  std::vector<int> v3;
};

Ideally, I would be able to access the flat_tuple_container either element-wise, e.g.

c.get<A>(3) = 4.4;

if I need the raw performance of contiguous memory, or tuple-wise, e.g.

auto t = c[3];
get<A>(t) = 4.4;

if I need the convenience of treating the nth entry of the container as a struct/class instance.

  • Why don't you want to store values in a 3 separate containers? – Sergii Khaperskov Mar 01 '14 at 06:53
  • 1
    Several reasons, actually (if it really matters): unify memory allocation; ensure consistency between fields; easy passing around as function arguments; allow array-of-struct convenience when desired, provide struct-of-arrays performance where needed. – Michael Schlottke-Lakemper Mar 01 '14 at 07:26
  • related to [vector-template-c-class-adding-to-vector](http://stackoverflow.com/questions/20971819/vector-template-c-class-adding-to-vector) – Jarod42 Mar 01 '14 at 09:54

3 Answers3

0

If I read your question correctly, then I believe this should work. I was able to compile and run it on Coliru (gcc 4.8).

  //Contains T::type elements
template<class T> struct Vector{
  std::vector<typename T::type> c;
  Vector(std::size_t n) : c(n){}
  void push_back(const typename T::type& e){ c.push_back(e); }
  typename T::type& get(std::size_t i){ return c[i]; }
};

//Find the index of a type in a pack
template <class T, class... Args> struct IndexOf;

template <class T, class... Args>
struct IndexOf<T, T, Args...> : std::integral_constant<std::size_t, 0> {};

template <class T, class Head,  class... Args>
struct IndexOf<T, Head, Args...> : std::integral_constant<std::size_t, IndexOf<T, Args...>::value + 1> {};

template<template<class> class Cont, class... Args>
struct Flat : Cont<Args>...{
  Flat(std::size_t n = 0) : Cont<Args>(n)...{}

  template<class T> void push_back(const typename T::type& t){
    Cont<T>::push_back(t);
  }
  template<class T> typename T::type& get(std::size_t i){
    return Cont<T>::get(i);
  }
  template<class T> static typename T::type fake();    //provide a way to see the type of get<T>
  template<class T> static typename T::type& fakeRef();

  typedef decltype( std::make_tuple(fake<Args>()...) ) Tuple;
  typedef decltype( std::tie(fakeRef<Args>()...) ) TupleRef;

  TupleRef operator[](std::size_t i){
      return TupleRef(get<Args>(i)...);
  }
  //you can make a const version that returns a Tuple if you want.

  template<class T> typename T::type& get(TupleRef t) const{
    return std::get<IndexOf<T,Args...>::value>(t);   
  }

};

It provides tuple access but not insertion. Here is an example

struct A{ typedef double type; };
struct B{ typedef int type; };
struct C{ typedef int type; };

int main(){
    typedef Flat<Vector, A, B, C> Data;
    Data d;

    d.push_back<A>(3.14);
    d.push_back<B>(1);
    d.push_back<C>(2);
    d.get<A>(0);
    d.get<A>(d[0]) = 2.78;
}
user783920
  • 158
  • 5
0

Following will solve your problem (need C++11):

#if 1 // std::get<T>(tuple) is not in C++11

// Helper to retrieve index to be able to call std::get<N>(tuple)
template <typename T, typename ... Ts> struct get_index;

template <typename T, typename ... Ts>
struct get_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename Tail,  typename ... Ts>
struct get_index<T, Tail, Ts...> :
    std::integral_constant<std::size_t, 1 + get_index<T, Ts...>::value> {};
#endif

template <typename ... Ts>
class Data
{
public:
    template <typename T>
    void push_back(const T& x) { return getVector<T>().push_back(x); }

    template <typename T>
    std::size_t size() const { return getVector<T>().size(); }

private:
    template <typename T>
    const std::vector<T>& getVector() const { return std::get<get_index<T, Ts...>::value>(items); }

    template <typename T>
    std::vector<T>& getVector() { return std::get<get_index<T, Ts...>::value>(items); }
private:
    std::tuple<std::vector<Ts>...> items;
};

Test it:

class A{};
class B{};

int main()
{
    Data<A, B> d;

    d.push_back(A());
    d.push_back(B());
    return 0;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Check the NVIDIA's Thrust library. It has a zip_iterator: An iterator which takes an arbitrary number of sequences, and iterating them looks like one single sequence of tuples of the contents of each elements of the original sequences. That is, works like a virtual tuple sequence, but there are really different sequences:

#include <thrust/iterator/zip_iterator.h> 
... 
// initialize vectors 
thrust::device_vector<int> A(3); 
thrust::device_vector<char> B(3);       
A[0] = 10; A[1] = 20; A[2] = 30; 
B[0] = 'x'; B[1] = 'y'; B[2] = 'z'; 

// create iterator (type omitted) 
first = thrust::make_zip_iterator(thrust::make_tuple(A.begin(), B.begin())); 
last = thrust::make_zip_iterator(thrust::make_tuple(A.end(), B.end())); 
first[0] // returns tuple(10, 'x') 
first[1] // returns tuple(20, 'y') 
first[2] // returns tuple(30, 'z') 

// maximum of [first, last) 
thrust::maximum< tuple<int,char> > binary_op; 
thrust::tuple<int,char> init = first[0]; 
thrust::reduce(first, last, init, binary_op); // returns tuple(30, 'z')
Manu343726
  • 13,969
  • 4
  • 40
  • 75