2

Is it possible to create a "sparse" variable-length 2D array using only variadic templates and std::array? By sparse, I mean that each row could have a variable amount of columns.

Ideally, I'd like to instantiate something like VariableLengthTable<int,2,4,3>() and have it create an array of arrays that would map to something like {{0,1}, {0,1,2,3}, {0,1,2}}. I've seen SO posts that deal with variadic templates to create multidimensional arrays, but they are all symmetrical.

Note that I'm limited to zero-cost abstractions (e.g. std::array) as I'm working with a very memory constricted application.

2
  • 1
    I'm not really sure you need a variadic template to do that. Isn't std::vector<std::vector<int>> or at least std::array<std::vector<int>,N> an option? Commented Feb 8, 2017 at 18:34
  • I'm looking for something that is zero-cost or very, very low cost. std::vector is unfortunately too big for our application. Commented Feb 8, 2017 at 21:18

1 Answer 1

3

Yes, this should be possible in principle. Here's a bit to get you started:

template <class T, std::size_t... Dim>
using VariableLengthTable = std::tuple<std::array<T, Dim>...>;

This is a tuple of std::arrays, each with a length specified by one of the template arguments.

Note that because the length of a std::array is part of its type, the first dimension cannot be an array, since its members would need different types. However, std::tuple works nicely. As long as you're willing to use std::get<i> instead of [i], and restrict yourself to compile-time is, you should be good.

If a compile-time i is not enough, you have two options:

One, use VariableLengthTable as above and add a runtime-to-compiletime conversion. Conceptually, something like this switch:

T& get(std::size_t x, std::size_t y)
{
  switch (x) {
    case 0: return std::get<0>(varLenTable)[y];
    case 1: return std::get<1>(varLenTable)[y];
    case 2: return std::get<2>(varLenTable)[y];
    // and so on
  }
}

In reality, you'd probably need to compose this using recursion or inheritance to avoid a compile-time out-of-bounds access. Boost.Preprocessor might help with that.

Two, store all the data in one sequential buffer and index into it at runtime. Something like this:

template <class T, std::size_t... Dim>
class VariableLengthTable
{
  static const std::array<std::size_t, sizeof...(Dim)> dims = {{ Dim... }};

  static const std::array<std::size_t, sizeof...(Dim)> cumulDims = [](){
    std::array<std::size_t, sizeof...(Dim)> result;
    result[0] = 0;
    for (std::size_t idx = 1; idx < sizeof...(Dim); ++idx) {
      result[idx] = result[idx - 1] + dims[idx];
    }
    return result;
  }();

  std::array<T, cumulDims[sizeof...(Dim) - 1] + dims[sizeof...(Dim) - 1]> data;

public:
  T& get(std::size_t x, std::size_t y)
  {
    return data[cumulDims[x] + y];
  }
};

The code above is to show the principle, not guaranteed to compile as-is.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, this might be exactly what I'm looking for. I wasn't that familiar with tuples. I'll give it a try!
Upon further inspection, I don't think I'll be able to use this in my application because of std::tuple's restriction to compile-time is. It's a cool solution, but it unfortunately won't work functionally the same as an array of arrays at runtime. I suppose I could add a template function to access cells in the form of get<X>(Y), but in a space-constrained application, that doesn't seem ideal.
@WilliamMoy I've added two ways to support runtime indexing.
Thanks for the update. #2 seems like the path I'll likely try. Answered!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.