【问题标题】:Find contractions of a variadic pack based on another variadic parameter pack根据另一个可变参数包查找可变参数包的收缩
【发布时间】:2016-05-17 12:58:07
【问题描述】:

我正在研究一个静态多维数组收缩框架,我遇到了一个有点难以解释的问题,但我会尽力而为。假设我们有一个N 维数组类

template<typename T, int ... dims>
class Array {}

可以实例化为

Array<double> scalar;
Array<double,4> vector_of_4s;
Array<float,2,3> matrix_of_2_by_3;
// and so on

现在我们有了另一个名为Indices的类

template<int ... Idx>
struct Indices {}

我现在有一个函数contraction,它的签名应该如下所示

template<T, int ... Dims, int ... Idx, 
typename std::enable_if<sizeof...(Dims)==sizeof...(Idx),bool>::type=0>
Array<T,apply_to_dims<Dims...,do_contract<Idx...>>> 
contraction(const Indices<Idx...> &idx, const Array<T,Dims...> &a)

我可能没有在这里得到语法,但我基本上希望返回的Array 具有基于Indices 条目的维度。让我提供contraction 可以执行的示例。请注意,在这种情况下,收缩意味着删除索引列表中参数相等的维度

auto arr = contraction(Indices<0,0>, Array<double,3,3>) 
// arr is Array<double> as both indices contract 0==0

auto arr = contraction(Indices<0,1>, Array<double,3,3>) 
// arr is Array<double,3,3> as no contraction happens here, 0!=1

auto arr = contraction(Indices<0,1,0>, Array<double,3,4,3>) 
// arr is Array<double,4> as 1st and 3rd indices contract 0==0  

auto arr = contraction(Indices<0,1,0,7,7,2>, Array<double,3,4,3,5,5,6>) 
// arr is Array<double,4,6> as (1st and 3rd, 0==0) and (4th and 5th, 7==7) indices contract

auto arr = contraction(Indices<10,10,2,3>, Array<double,5,6,4,4>
// should not compile as contraction between 1st and 2nd arguments 
// requested but dimensions don't match 5!=6

// The parameters of Indices really do not matter as long as 
// we can identify contractions. They are typically expressed as enums, I,J,K...

所以本质上,鉴于Idx...Dims... 的大小应该相等,检查Idx... 中的哪些值相等,获取它们出现的位置并删除@987654336 中的相应条目(位置) @。这本质上是一个tensor contraction rule

数组收缩规则:

  1. 索引的参数个数和数组的维度/等级应该相同,即sizeof...(Idx)==sizeof...(Dims)
  2. IdxDims之间存在一对一对应关系,即如果我们有Indices&lt;0,1,2&gt;Array&lt;double,4,5,6&gt;0映射到41映射到52 映射到6
  3. 如果Idx 中有相同/相等的值,则意味着收缩,这意味着Dims 中的相应维度应该消失,例如,如果我们有Indices&lt;0,0,3&gt;Array&lt;double,4,4,6&gt;,那么0==0 和这些值映射到的对应维度是 44 都需要消失,结果数组应该是 Array&lt;double,6&gt;
  4. 如果Idx具有相同的值,但对应的Dims不匹配,则应触发编译时错误,例如Indices&lt;0,0,3&gt;Array&lt;double,4,5,6&gt;不可能为4!=5,类似Indices&lt;0,1,0&gt; 不可能像 4!=6,这会导致
  5. 不同维度的数组不能收缩,例如Array&lt;double,4,5,6&gt;不能以任何方式收缩。
  6. 只要对应的Dims 也匹配,Idx 允许多个对、三胞胎、四胞胎等,例如,Indices&lt;0,0,0,0,1,1,4,3,3,7,7,7&gt; 将收缩为Array&lt;double,6&gt;,假设输入数组为@987654368 @。

我对元编程的了解并没有达到这个功能,但我希望我已经明确了意图,以便有人指导我朝着正确的方向前进。

【问题讨论】:

  • 我无法弄清楚你的收缩规则是什么。给定Idx...Dims...,输出尺寸应该是多少?你能提供一套规则,而不是一套例子吗?
  • 本质上,给定Idx...Dims... 的大小应该相等,检查Idx... 中的哪些值相等,获取它们出现的位置并删除@ 中的相应条目987654374@.
  • @romeric - 你在Idx 中只能有几个相等的值,甚至是三胞胎等?如果是三胞胎,规则是什么?
  • 潜在地,你可以有尽可能多的相等值,例如对于三元组contraction(Indices&lt;0,0,1,1,2,2,3&gt;, Array&lt;double,3,3,4,4,5,5,6&gt;) 将给出Array&lt;double,6&gt; 为 (1st and 2nd, 0==0), (3rd and 4th, 1== 1), (5th and 6th, 2==2) 将全部收缩并消失。
  • 我可以有索引 吗?

标签: c++ c++11 multidimensional-array template-meta-programming


【解决方案1】:

一组进行实际检查的constexpr 函数:

// is ind[i] unique in ind?
template<size_t N>
constexpr bool is_uniq(const int (&ind)[N], size_t i, size_t cur = 0){
    return cur == N ? true : 
           (cur == i || ind[cur] != ind[i]) ? is_uniq(ind, i, cur + 1) : false;
}

// For every i where ind[i] == index, is dim[i] == dimension?
template<size_t N>
constexpr bool check_all_eq(int index, int dimension,
                            const int (&ind)[N], const int (&dim)[N], size_t cur = 0) {
    return cur == N ? true :
           (ind[cur] != index || dim[cur] == dimension) ? 
                check_all_eq(index, dimension, ind, dim, cur + 1) : false;
}

// if position i should be contracted away, return -1, otherwise return dim[i].
// triggers a compile-time error when used in a constant expression on mismatch.
template<size_t N>
constexpr int calc(size_t i, const int (&ind)[N], const int (&dim)[N]){
    return is_uniq(ind, i) ? dim[i] :
           check_all_eq(ind[i], dim[i], ind, dim) ? -1 : throw "dimension mismatch";
}

现在我们需要一种方法来摆脱-1s:

template<class Ind, class... Inds>
struct concat { using type = Ind; };
template<int... I1, int... I2, class... Inds>
struct concat<Indices<I1...>, Indices<I2...>, Inds...>
    :  concat<Indices<I1..., I2...>, Inds...> {};

// filter out all instances of I from Is...,
// return the rest as an Indices    
template<int I, int... Is>
struct filter
    :  concat<typename std::conditional<Is == I, Indices<>, Indices<Is>>::type...> {};

使用它们:

template<class Ind, class Arr, class Seq>
struct contraction_impl;

template<class T, int... Ind, int... Dim, size_t... Seq>
struct contraction_impl<Indices<Ind...>, Array<T, Dim...>, std::index_sequence<Seq...>>{
    static constexpr int ind[] = { Ind... };
    static constexpr int dim[] = { Dim... };
    static constexpr int result[] = {calc(Seq, ind, dim)...};

    template<int... Dims>
    static auto unpack_helper(Indices<Dims...>) -> Array<T, Dims...>;

    using type = decltype(unpack_helper(typename filter<-1,  result[Seq]...>::type{}));
};


template<class T, int ... Dims, int ... Idx, 
typename std::enable_if<sizeof...(Dims)==sizeof...(Idx),bool>::type=0>
typename contraction_impl<Indices<Idx...>, Array<T,Dims...>, 
                          std::make_index_sequence<sizeof...(Dims)>>::type
contraction(const Indices<Idx...> &idx, const Array<T,Dims...> &a);

除了make_index_sequence 之外的所有内容都是 C++11。你可以在 SO 上找到无数的实现。

【讨论】:

  • 我从没想过将索引序列作为 constexpr 列表初始化器传递。整洁!
  • @T.C.无法让您的解决方案在 -std=c++11 下运行。使用-std=c++14 编译良好。我正在为make_index_sequence 使用this 实现。
  • @romeric 这不是一个正确的实现,但是如果你想使用它,你需要typename make_index_sequence&lt;S&gt;::type
【解决方案2】:

这是一团糟,但我认为它可以满足您的需求。几乎可以肯定,可以对此进行许多简化,但这是我第一次通过测试。请注意,这不会实现收缩,而只是确定类型应该是什么。如果这不是您需要的,我提前道歉。

#include <type_traits>

template <std::size_t...>
struct Indices {};

template <typename, std::size_t...>
struct Array {};

// Count number of 'i' in 'rest...', base case
template <std::size_t i, std::size_t... rest>
struct Count : std::integral_constant<std::size_t, 0>
{};

// Count number of 'i' in 'rest...', inductive case
template <std::size_t i, std::size_t j, std::size_t... rest>
struct Count<i, j, rest...> :
    std::integral_constant<std::size_t,
                           Count<i, rest...>::value + ((i == j) ? 1 : 0)>
{};

// Is 'i' contained in 'rest...'?
template <std::size_t i, std::size_t... rest>
struct Contains :
    std::integral_constant<bool, (Count<i, rest...>::value > 0)>
{};


// Accumulation of counts of indices in all, base case
template <typename All, typename Remainder,
          typename AccIdx, typename AccCount>
struct Counts {
    using indices = AccIdx;
    using counts = AccCount;
};

// Accumulation of counts of indices in all, inductive case
template <std::size_t... all, std::size_t i, std::size_t... rest,
          std::size_t... indices, std::size_t... counts>
struct Counts<Indices<all...>, Indices<i, rest...>,
              Indices<indices...>, Indices<counts...>>
    : std::conditional<Contains<i, indices...>::value,
                       Counts<Indices<all...>, Indices<rest...>,
                              Indices<indices...>,
                              Indices<counts...>>,
                       Counts<Indices<all...>, Indices<rest...>,
                              Indices<indices..., i>,
                              Indices<counts...,
                                      Count<i, all...>::value>>>::type
{};

// Get value in From that matched the first value of Idx that matched idx
template <std::size_t idx, typename Idx, typename From>
struct First : std::integral_constant<std::size_t, 0>
{};
template <std::size_t i, std::size_t j, std::size_t k,
          std::size_t... indices, std::size_t... values>
struct First<i, Indices<j, indices...>, Indices<k, values...>>
    : std::conditional<i == j,
                       std::integral_constant<std::size_t, k>,
                       First<i, Indices<indices...>,
                             Indices<values...>>>::type
{};

// Return whether all values in From that match Idx being idx are tgt
template <std::size_t idx, std::size_t tgt, typename Idx, typename From>
struct AllMatchTarget : std::true_type
{};
template <std::size_t idx, std::size_t tgt,
          std::size_t i, std::size_t j,
          std::size_t... indices, std::size_t... values>
struct AllMatchTarget<idx, tgt,
                      Indices<i, indices...>, Indices<j, values...>>
    : std::conditional<i == idx && j != tgt, std::false_type,
                       AllMatchTarget<idx, tgt, Indices<indices...>,
                                      Indices<values...>>>::type
{};

/* Generate the dimensions, given the counts, indices, and values */
template <typename Counts, typename Indices,
          typename AllIndices, typename Values, typename Accum>
struct GenDims;

template <typename A, typename V, typename R>
struct GenDims<Indices<>, Indices<>, A, V, R> {
    using type = R;
};
template <typename T, std::size_t i, std::size_t c,
          std::size_t... counts, std::size_t... indices,
          std::size_t... dims, typename AllIndices, typename Values>
struct GenDims<Indices<c, counts...>, Indices<i, indices...>,
               AllIndices, Values, Array<T, dims...>>
{
    static constexpr auto value = First<i, AllIndices, Values>::value;
    static_assert(AllMatchTarget<i, value, AllIndices, Values>::value,
                  "Index doesn't correspond to matching dimensions");
    using type = typename GenDims<
        Indices<counts...>, Indices<indices...>,
        AllIndices, Values,
        typename std::conditional<c == 1,
                                  Array<T, dims..., value>,
                                  Array<T, dims...>>::type>::type;
};

/* Put it all together */
template <typename I, typename A>
struct ContractionType;

template <typename T, std::size_t... indices, std::size_t... values>
struct ContractionType<Indices<indices...>, Array<T, values...>> {
    static_assert(sizeof...(indices) == sizeof...(values),
                   "Number of indices and dimensions do not match");
    using counts = Counts<Indices<indices...>,
                          Indices<indices...>,
                          Indices<>, Indices<>>;
    using type = typename GenDims<typename counts::counts,
                                  typename counts::indices,
                                  Indices<indices...>, Indices<values...>,
                                  Array<T>>::type;
};

static_assert(std::is_same<typename
              ContractionType<Indices<0, 0>, Array<double, 3, 3>>::type,
              Array<double>>::value, "");
static_assert(std::is_same<typename
              ContractionType<Indices<0, 1>, Array<double, 3, 3>>::type,
              Array<double, 3, 3>>::value, "");
static_assert(std::is_same<typename
              ContractionType<Indices<0, 1, 0>, Array<double, 3, 4, 3>>::type,
              Array<double, 4>>::value, "");
static_assert(std::is_same<typename
              ContractionType<Indices<0, 1, 0, 7, 7, 2>,
              Array<double, 3, 4, 3, 5, 5, 6>>::type,
              Array<double, 4, 6>>::value, "");

// Errors appropriately when uncommented
/* static_assert(std::is_same<typename */
/*               ContractionType<Indices<10,10, 2, 3>, */
/*               Array<double, 5,6,4,4>>::type, */
/*               Array<double>::value, ""); */

下面是对这里发生的事情的解释:

  • 首先,我使用Counts 生成唯一索引列表 (Counts::indices) 以及每个索引在序列中出现的次数 (Counts::counts)。
  • 然后我遍历索引,计算来自Counts 的对,对于每个索引,如果计数为 1,我将值累加并递归。否则,我传递累积值并递归。

最烦人的部分是GenDims中的static_assert,它会验证所有匹配维度都相同的索引。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-29
    相关资源
    最近更新 更多