使用索引技巧进行多维数组初始化

时间:2012-10-15 06:55:08

标签: c++ templates c++11

我的问题提到了我之前问过的一维问题:

for loop elimination

有人可以帮助我将指数技巧的使用扩展到多维数组,例如在这个例子中:

template<unsigned...> struct indices
{
};

template<unsigned M, unsigned... Is> struct indices_gen
  : indices_gen<M - 1, M - 1, Is...>
{
};

template<unsigned... Is> struct indices_gen<0, Is...> : indices<Is...>
{
};

template <typename T>
struct example
{
  template<typename ...U, typename
    = typename std::enable_if<all_of<std::is_same<U, T>...>::value>::type>
  example(U... args)
  {
    static_assert(3 * 2 == sizeof...(U),
      "wrong number of arguments in assignment");
    assign(indices_gen<M * N>(), args...);
  }

  template<size_type... Is, class... U>
  void assign(indices<Is...>, U... args)
  {
    [](...){}(((&array[0][0])[Is] = args)...);
  }

  T array[3][2];
};

int main()
{
  example<int> ex(1, 2, 3, 4, 5, 6);
  return 0;
}

目前我依赖于要求,数组是连续的,但是我想使用索引对分配array,而不仅仅是单个索引(这样我就可以支持除了数组,特别是覆盖operator[])的类型。如果我使用2个参数包进行赋值,我只会在索引(0,0),(1,1),...中进行分配,同样存在一个小问题,当参数包的长度不同时,参数包的长度不同array不同(如示例所示)。

1 个答案:

答案 0 :(得分:4)

通过更改访问数组的代码而不是索引生成,可以更轻松地做到这一点。您基本上想要的是1D到2D访问映射。

通常情况下,人们需要反过来(2D到1D),当他们用一维数组实现二维数组时:

template<class T, unsigned M, unsigned N>
struct md_array{
  T elems[M * N]; // same layout as 'T elems[M][N];'
};

从二维索引i获取一维索引(x,y)的公式为i == x * N + y。如果我们将上面的1D elems想象为2D数组(使用M == 2N == 3),我们就可以解释这一点:

 0, 1, 2, 3, 4, 5 // indices (i)
[a, b, c, d, e, f]
v // we want them as M (2) packs of N (3) elements
 0, 1, 2   0, 1, 2 // element indices (y)
[a, b, c] [d, e, f]
\___0___/ \___1___/ // pack indices (x)
v // fusing the packs back together, we can see that we have
  // a constant offset for the packs, which is the N (3) times x
0*3+0, 0*3+1, 0*3+2, 1*3+0, 1*3+1, 1*3+2
[ a,     b,     c,     d,     e,     f ]

因此,我们得到i == x * N + y。我们现在需要为i而不是xy解决此公式。对于x,它很容易(使用数学符号):

i = x * N + y | -y
i - y = x * N | *(1/N)
i - y
----- = x
  N

所以x == (i - y) / N。现在,遗憾的是,我不知道如何使用纯数学来解决y,但我们不需要这样。看一下元素索引,我们可以看到它们环绕N,并且可以使用模运算符轻松完成。因此,y == i % N

现在我们可以实现一个方法,该方法采用线性索引i并返回解析后的(x, y)元素:

template<unsigned I>
T& get(){ constexpr auto y = I % 3; return array[(I-y)/3][y]; }

并概括说明:

template<unsigned I>
T& get(){ constexpr auto y = I % N, x = (I-y)/N; return array[x][y]; }

使用constexpr确保所有计算都在编译时完成。 现在您只需按以下方式编写assign

template<unsigned... Is, class... U>
void assign(indices<Is...>, U... args)
{
  [](...){}((get<Is>() = args)...);
}

Q.E.D。 (Live example.


†现在,您可以通过将2D数组实际实现为一维数组来使自己更容易。 :)这样,您可以直接使用(array[Is] = args)...,而对于其他情况,可以使用执行映射的简单访问函数:

T& get(unsigned x, unsigned y){ return array[x * N + y]; }

Live example.