1

I'd like to create such a structure:

struct Arrays{
    typedef unsigned char byte_t;

    std::array<byte_t, X>& get(int x, int y)
    {
        switch(x){
            case 1: return arr_1.at(y);
            case 2: return arr_2.at(y);
            ... up to ...
            case 50: return arr_50.at(y);
            default: break;
        }
    }

    std::vector<std::array<byte_t, 1>> arr_1;
    std::vector<std::array<byte_t, 2>> arr_2;
    ... up to ....
    std::vector<std::array<byte_t, 50>> arr_50;
};

I know that

std::array<byte_t, 1>

and

std::array<byte_t, 2>

are different structures, so this is impossible in this way. How can I managed with it in different manner? Do I have to return byte_t* from the method?

hmjd
  • 120,187
  • 20
  • 207
  • 252
Dejwi
  • 4,393
  • 12
  • 45
  • 74
  • 8
    erm, return a `std::vector<>`? – Nim Jun 25 '13 at 07:40
  • I forgot about an &. I have to have an ability to modify this array. – Dejwi Jun 25 '13 at 07:51
  • you can return a reference to the vector, and modify it to your hearts content... – Nim Jun 25 '13 at 07:57
  • Yes. But as far as I understand, you suggest to create a copy of an std::array inside a std::vector and return a vector. The oryginal data cannot be modified in this way. – Dejwi Jun 25 '13 at 08:00
  • No, instead of using `std::array<>` in your `arr_1` et al, use a `std::vector<>`, e.g. `std::vector> arr_1;` then return a reference to this.. – Nim Jun 25 '13 at 08:02
  • It is definitely not an option due to a huge memory overhead. – Dejwi Jun 25 '13 at 08:28
  • what "huge memory overhead"? A vector object (IIRC in gcc is 24 bytes), this will be in addition to the size of the content (which should be the same as that of your arrays), so the overhead for using vector is 24 * 50 * N where N is the depth of each member you have, btw - using a switch is not very smart here, as you can simply declare a three dimensional vector here! `std::vector>> _data`, then your function becomes, `return _data[x][y];` (you'll have to check that both `x`/`y` exist... – Nim Jun 25 '13 at 08:40
  • Yes and every nested vector takes an additional 3*word size. So inside a std::vector I will store for example 3 bytes of data (equivalent for std::array) and have an additional 24bytes for a vector structure. – Dejwi Jun 25 '13 at 08:45
  • Okay, so are you running this on some embedded system where this overhead is a concern? If so, then the the best option here is to return this, `std::pair`, you should be able to figure out what goes there... Else, stick to what makes your code the easiest to read/understand... – Nim Jun 25 '13 at 08:49
  • No it is not an embeded system. But in a complex structure like vector> when the outer vector contains 1M elements it is visible overhead. The pair of pointers seems interesting. – Dejwi Jun 25 '13 at 09:03

2 Answers2

2

As the previous comments have said, the array size is a compile-time constant. The only way to achieve this then is to pass x as a template argument. How about the following? (For convenience, I have defined a class vector_of_arrays to hold your vectors arr_1 to arr_50 in such a way that I can refer to each one by the number rather than having to switch and give a name)

#include <array>
#include <type_traits>
#include <vector>

template <typename type, size_t max_size>
class vector_of_arrays : public std::vector<std::array<type, max_size>>
{
private:
    vector_of_arrays<type, max_size - 1> _next;

public:
    template <size_t size>
    typename std::enable_if<(size < max_size), vector_of_arrays<type, size>&>::type
    get_vector_for_size()
    {
        return _next.get_vector_for_size<size>();
    }

    template <size_t size>
    typename std::enable_if<(size == max_size), vector_of_arrays<type, size>&>::type
    get_vector_for_size()
    {
        return *this;
    }
};

template <typename type>
class vector_of_arrays<type, 1> : public std::vector<std::array<type, 1>>
{
public:
    template <size_t size>
    typename std::enable_if<(size == 1), vector_of_arrays<type, size>&>::type
    get_vector_for_size()
    {
        return *this;
    }
};

struct arrays
{
    typedef unsigned char byte_t;
    vector_of_arrays<byte_t, 50> va;

    template <size_t x>
    std::array<byte_t, x>& emplace_array()
    {
        va.get_vector_for_size<x>().emplace_back();
        return *(va.get_vector_for_size<x>().end() - 1);
    }

    template <size_t x>
    std::array<byte_t, x>& get(int y)
    {
        return va.get_vector_for_size<x>().at(y);
    }
};

To test this code, you could do something like

arrays foo;
auto& arr1 = foo.emplace_array<3>();
arr1[0] = 1.;
arr1[1] = 2.;
arr1[2] = 3.;

auto& arr2 = foo.get<3>(0);
std::cout << arr2[0] << ' ' << arr2[1] << ' ' << arr2[2] << std::endl;
Saran Tunyasuvunakool
  • 1,064
  • 1
  • 9
  • 23
  • The OP has compile time constants -- `1` through `50`, in the `switch`/`case` statement. The issue isn't the lack of compile time constants in general, but compile time constants *outside the body of the function*. – Yakk - Adam Nevraumont Jun 25 '13 at 13:03
  • Sure, but I sort of interpreted the original post to mean that the parameter `x` can be expected to be a compile-time constant wherever the function is called. – Saran Tunyasuvunakool Jun 25 '13 at 13:15
1

First, you are violating DRY.

Replace this:

std::vector<std::array<byte_t, 1>> arr_1;
std::vector<std::array<byte_t, 2>> arr_2;
... up to ....
std::vector<std::array<byte_t, 50>> arr_50;

with something like:

template<typename Pack, unsigned X> struct append;
template<template<unsigned...>class Pack, unsigned... Xs, unsigned X>
struct append<Pack<Xs...>, X> {
  typedef Pack<Xs..., X> type;
};
template<typename Pack, unsigned X> using Append=typename append<Pack,X>::type;


template<unsigned N, template<unsigned>class Contents>
struct auto_tuple {
  typedef Append< typename auto_tuple<N-1, Contents>::type, Contents<N-1> > type;
};
template<template<unsigned>class Contents>
struct auto_tuple<0, Contents> {
  typedef std::tuple<> type;
};
template<unsigned N, template<unsigned>class Contents>
using AutoTuple = typename auto_tuple<N,Contents>::type;

where AutoTuple<N, Contents> applies 0 through N-1 to Contents and generates a std::tuple out of it. Write:

template<typename T, unsigned N>
using vec_of_array = std::vector<std::array<T, N>>;
template<unsigned N>
using vec_of_byte_array = vec_of_array<byte_t, N>;
template<unsigned N>
using get_nth_byte_array = vec_of_byte_array<N+1>;

which you use to populate your tuple like:

typedef AutoTuple<50, get_nth_byte_array> my_tuple;

which is a std::tuple of 50 different std::vectors, each of which store a std::array of 1 through 50 bytes.

This took far fewer than 50 lines, and while this will be harder to get it to work and understand it, it means that the code is uniform and generated once: there is far less of a chance that one particular line is wrong, and a far higher chance that everything is wrong at once. And you can use a compile-time value to extract the nth element via std::get<7>(my_tuple). Oh, and if you want 100 or 10 instead of 50? Change one constant.

Next, a contiguous_range structure that is basically a dressed up pair of pointers gives you a type-erased way to look at an array (or other buffer) of N elements, like Passing a std::array of unknown size to a function

Now, you can either write your big switch statement manually, or you can build a table of function pointers that extract the contiguous_range and build it automatically.

// DataStorage is the tuple of std::vector of std::array
template<unsigned...> struct seq{};
template<unsigned max, unsigned... s> struct make_seq:make_seq<max-1, max-1, s...> {};
template<unsigned... s> struct make_seq<0,s...>:seq<s...> {};

template<unsigned N>
struct get_array {
  static contig_range<byte> get(DataStorage& data, unsinged idx) {
    return ( std::get<N>( std::forward<Data>(data) )[idx];
  }
};

and build an array of 50 of them (each with a different N) stored in an array of:

typedef contig_range<byte> (*array_getter)( DataStorage&, unsigned idx );
template<unsigned N, unsigned... S>
std::array< array_getter, N > populate_array_helper( seq<S...> ) {
  return { get_array<S>::get... };
}
template<unsigned N>
std::array< array_getter, N > populate_array() {
  return populate_array_helper( make_seq<N>() );
}

which you can then use the run-time argument to lookup, call it, and you get a type-erased instance of your contiguous array of byte.

Non-code overhead is 50 pointers (for the lookup table), 3 pointers (for the containing vector).

And you never do a cpoy/paste of the same code 50 times over with a single number changed each time, one of which has a slight typo in it that generates a subtle fenceposting bug that only occurs in one obscure, hard to reproduce test case in release mode only.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524