C++20 将多维数组的维度推导到参数包中

C++20 Deducing the dimensions of a multidimensional array into a parameter pack

我正在以多维数组的形式实现一个容器。我正在尝试复制 std::to_array (C++ 20),一个使用我的多维数组从一维内置数组创建 std::array 的函数:

template<typename T, std::size_t... N>
constexpr mdarray<std::remove_cv_t<T>, N...> to_mdarray(T (&a)[N]));

int arr[5][12]{};
to_mdarray(arr); // Returns mdarray<int, 5, 12>

我想将内置数组的维度推导为N和return对应的mdarray。我没有弄清楚正确的语法,我总是以错误告终。要么我没有正确展开包,要么编译器认为我正在尝试声明函数指针或 lambda。我也没有在网上找到任何资源。

到目前为止我找到了两个替代方案。一种是将数组衰减为指针并让用户指定维度。

template<typename T, std::size_t... N>
constexpr mdarray<std::remove_cv_t<T>, N...> to_mdarray(T* const a);

int arr[5][12]{};
to_mdarray<5, 12>(arr);

我在 blog from Stanislav Arnaudov 中找到了另一个。它涉及使用递归宏生成第 N 维重载:

#define SIZE_T_S_1 size_t N
#define SIZE_T_S_2 SIZE_T_S_1 , size_t M
#define SIZE_T_S_3 SIZE_T_S_2 , size_t L

#define BRACKETS_S_1 [N]
#define BRACKETS_S_2 BRACKETS_S_1[M]
#define BRACKETS_S_3 BRACKETS_S_2[L]

#define LETTERS_S_1 N
#define LETTERS_S_2 LETTERS_S_1, M
#define LETTERS_S_3 LETTERS_S_2, L

#define TO_MD_N(dim) template<typename T, SIZE_T_S_##dim>                                       \
constexpr mdarray<std::remove_cv_t<T>, LETTERS_S_##dim> to_mdarray(T (&values) BRACKETS_S_##dim);

TO_MD_N(1);
TO_MD_N(2);
TO_MD_N(3);

// Equivalent to:
// TO_MD_N(1)
template<typename T, std::size_t N>
constexpr mdarray<std::remove_cv_t<T>, N> to_mdarray(T (&values)[N]);

// TO_MD_N(2)
template<typename T, std::size_t, std::size_t M>
constexpr mdarray<std::remove_cv_t<T>, N, M> to_mdarray(T (&values)[N][M]);

// TO_MD_N(3)
template<typename T, std::size_t, std::size_t M, std::size_t L>
constexpr mdarray<std::remove_cv_t<T>, N, M, L> to_mdarray(T (&values)[N][M][L]);

这两种解决方案都有缺陷。一种要求用户输入正确的维度,另一种要求多次定义同一个函数并限制维度的个数。

有没有办法在 C++20 中做到这一点,或者目前不可能?

不幸的是,我一贯的 Boost.Mp11 单行趋势将在这里结束,因为 Boost.Mp11 并没有真正很好地处理 values,我们需要将 int[5][12] 转换为 mdarray<int, 5, 12> 并且没有简单的机制可以用那个库来做到这一点。

相反,我们只是手工完成。 std::extent gives you the Nth extent and std::rank 给出范围数。将它与 make_index_sequence 结合起来,你可以获得所有的 'em:

template <typename T, typename>
struct mdarray_for_impl;

template <typename A, size_t... Is>
struct mdarray_for_impl<A, std::index_sequence<Is...>> {
    using type = mdarray<std::remove_all_extents_t<A>, std::extent_v<A, Is>...>;
};

template <typename T>
using mdarray_for = mdarray_for_impl<T, std::make_index_sequence<std::rank_v<T>>>::type;

此处 mdarray_for<int[1][2][3]> 将产生类型 mdarray<int, 1, 2, 3>.


Boost.Mp11 版本无论如何需要几个辅助别名

// a type-only mdarray
template <typename T, typename... Ns>
using mdarray_t = mdarray<T, Ns::value...>;

// a type-only extent
template <typename T, typename V>
using mp_extent = mp_size_t<std::extent_v<T, V::value>>;

然后允许:

template <typename T>
using mdarray_for2 = 
    mp_apply<mdarray_t,
    mp_append<
        mp_list<std::remove_all_extents_t<T>>,
        mp_transform_q<
            mp_bind<mp_extent, T, _1>,
            mp_iota_c<std::rank_v<T>>>
        >>;

这与之前的算法相同,除了 mp_iota_c<std::rank_v<T>> 获取范围索引序列(而不是 std::make_index_sequence)然后 mp_transform_q 获取第 n 个范围(而不是直接在包扩展中使用它)。

在这种情况下,对于 int[5][12],我们构建 mp_list<int, mp_size_t<5>, mp_size_t<12>>(因为我们都是类型,没有值)然后 mp_apply 将其转换为 mdarray_t<...>它变成 mdarray<int, 5, 12>.

I would like to deduce the dimensions of the built-in array into N and return the corresponding mdarray.

也许...用一点递归...

template <std::size_t ... Ns, typename T>
constexpr mdarray<std::remove_cv_t<T>, Ns...> get_mdarray_t (T const &)
 { return {}; }

template <std::size_t ... Ns, typename T, std::size_t N>
constexpr auto get_mdarray_t (T const (&arr)[N])
 { return get_mdarray_t<Ns..., N>(arr[0]); }

你可以构建一个有用的using

template <typename T>
using mdarray_t = decltype(get_mdarray_t<>(std::declval<T>()));

以下是完整编译C++11的例子

#include <utility>

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

template <std::size_t ... Ns, typename T>
constexpr mdarray<std::remove_cv_t<T>, Ns...> get_mdarray_t (T const &)
 { return {}; }

template <std::size_t ... Ns, typename T, std::size_t N>
constexpr auto get_mdarray_t (T const (&arr)[N])
 { return get_mdarray_t<Ns..., N>(arr[0]); }

template <typename T>
using mdarray_t = decltype(get_mdarray_t<>(std::declval<T>()));

int main ()
 {
   using A0 = int;
   using A1 = int[2u];
   using A2 = int[2u][3u];
   using A3 = int[2u][3u][5u];

   using T0 = mdarray_t<A0>;
   using T1 = mdarray_t<A1>;
   using T2 = mdarray_t<A2>;
   using T3 = mdarray_t<A3>;

   using U0 = mdarray<int> ;
   using U1 = mdarray<int, 2u>;
   using U2 = mdarray<int, 2u, 3u>;
   using U3 = mdarray<int, 2u, 3u, 5u>;

   static_assert( std::is_same<T0, U0>::value, "!" );
   static_assert( std::is_same<T1, U1>::value, "!" );
   static_assert( std::is_same<T2, U2>::value, "!" );
   static_assert( std::is_same<T3, U3>::value, "!" );
 }