如何编写一个能够接受任意阶数组的函数或构造函数

7

我有一个封装向量和形状的结构体,如下所示:

template <std::size_t N>
struct array_type {
    std::array<std::size_t, N> shape;
    std::vector<float> data;
};

我希望能够从任何数组 float[N], float[N][M] 等构建一个 array_type
目前,我对于每个秩都有一个函数,例如:
template <std::size_t N>
array_type<1> _1d(const deel::formal::float_type (&values)[N]) {
    return {{N},
            std::vector<float_type>(
                reinterpret_cast<const float_type*>(values),
                reinterpret_cast<const float_type*>(values) + N)};
}

template <std::size_t N>
array_type<2> _2d(const deel::formal::float_type (&values)[N][M]) {
    return {{N, M},
            std::vector<float_type>(
                reinterpret_cast<const float_type*>(values),
                reinterpret_cast<const float_type*>(values) + N * M)};
}

我希望写一些类似这样的内容:

template <class Array>
array_type<std::rank_v<Array>> make_array(Array const&);

...但这对于初始化列表不起作用:

auto arr1 = _1d({1, 2, 3}); // Ok
auto arr2 = make_array({1, 2, 3}); // Ko

有没有一种方法可以创建这个make_array函数?或者(因为我认为这是不可能的)至少有像make_array<1>({1,2,3})这样显式指定秩的东西吗?
3个回答

2

array_type::shape 可以通过一些模板元编程和 std::extent 生成:

template<typename Array, std::size_t... I>
auto extents_impl(const Array& a, std::index_sequence<I...>)
{
    return std::array{std::extent_v<Array, I>...};
}

template<typename Array>
auto extents(const Array& a)
{
    return extents_impl(a, std::make_index_sequence<std::rank_v<Array>>());
}

有了这个,make_array 可以写成:

template <class Array, std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_all_extents_t<Array>>, float>, int> = 0> // magic incantation :)
array_type<std::rank_v<Array>> make_array(Array const& a)
{
    array_type<std::rank_v<Array>> ret {
        .shape = extents(a),
        .data = std::vector<float>(sizeof a / sizeof(float)),
    };
    std::memcpy(ret.data.data(), &a, sizeof a);
    return ret;
}

我使用memcpy来避免潜在的指针类型别名违规,以及根据标准的严格解释,在子数组边界之外迭代的技术性问题会导致未定义行为。

...但这对于初始化列表不起作用:

添加一个重载:

template <std::size_t N>
array_type<1> make_array(float const (&a)[N])
{
    return make_array<float[N]>(a);
}

另外,您可以在调用时指定数组类型:

make_array<float[2][3]>({{1, 2, 3},{4, 5, 6}});

这不需要过载。


谢谢您的回答,但正如我所解释的,这对于make_array({1, 2, 3})无法工作,因为模板类型Array是问题的关键。 - Holt
@Holt 噢。我认为整个重点是不必为每个等级编写单独的函数。 - eerorika
其实两者都有影响,但是在我的实现中阻塞点确实是无法推导的模板类型,不过我可能表述得不够清楚。 - Holt
谢谢您的编辑,但是每个等级仍需要一个重载(尽管每个重载都会稍微小一些)。此外,您能否澄清并指出这个问题的参考资料:我使用memcpy来避免潜在的指针类型别名违规,并且根据标准的严格解释,在子数组边界之外进行迭代的技术性问题属于UB - Holt

0

T (&&)[N]建议用于当您想让函数模板接受花括号初始化列表并推导其长度时,此外,您可以使用 T (&&)[M][N]T (&&)[M][N][O] ... 推导每个维度的长度(不幸的是,没有像 T (&&)[N]...[Z] 这样接受任意维度数组的东西)。

这样,您就可以提供像这样的重载函数:


// attention that `T (&&)[N][M]` is `Y (&&)[N]` where `Y` is `T[M]`.

template<typename T, size_t N>
using Array1D_t = T[N];
template<typename T, size_t M, size_t N>
using Array2D_t = Array1D_t<Array1D_t<T, N>, M>;
template<typename T, size_t M, size_t N, size_t O>
using Array3D_t = Array1D_t<Array2D_t<T, N, O>, M>;
// ...

// assume the max rank is X.

template<typename T, size_t N>
array_type<1> make_array(Array1D_t<T, N>&& v);
template<typename T, size_t M, size_t N>
array_type<2> make_array(Array2D_t<T, M, N>&& v);
template<typename T, size_t M, size_t N, size_t O>
array_type<3> make_array(Array3D_t<T, M, N, O>&& v);
// ...

然后,对于make_array({1, 2, 3})make_array({{1, 2, 3}, {4, 5, 6}})等情况都可以正常运行,除非秩大于X


谢谢,但这正是我现在正在做的,也是问题中已经存在的(我只有不同的函数名称,但那并不重要)。 - Holt
@Holt,我对你想要的东西感到困惑。T(&&)[N]是使其可推导的唯一方法,但它永远无法适应任意秩。你想要使用make_array<1>({1, 2, 3})make_array<2>({{1, 2, 3}})代替make_array({1, 2, 3})make_array({{1, 2, 3}}),为什么? - RedFog
好的,这很容易说,但不可能实现。原因就是我之前所说的:没有像 T (&&)[N]...[Z] 这样接受任意秩数组的东西。 你可以提出一个特性请求,要求允许 T (&&)...[Ns] 或类型别名的部分特化。 - RedFog
我不理解你的最后一条评论。关于你倒数第二条评论,你做了一个快捷方式,假设它是 T (&)[N]...[Z],是的,那是不可能的,但也许有一种方法可以使用中间结构,以便通过显式传递秩 R,你可以让 some_struct<R>::type 指向正确的数组类型(或者可能是接受正确的数组类型的函数)。我并不是说这是可能的,但你不应该假设它不可能,只是因为简单的方法不可行。 - Holt
我并不轻易否定你的想法,但我知道它既无法满足模板参数推导规则,也不能满足可推导花括号初始化列表的前提条件。事实上,我曾尝试过,但最终选择了重载函数。 - RedFog
显示剩余3条评论

0
有没有办法实现这个make_array?
我不认为有(但是,说实话,我无法证明它是不可能的)。
或者(因为我认为这是不可能的),至少有像make_array<1>({1, 2, 3})这样显式指定秩的东西吗?
在这种情况下,我也看不到任何方法。
但是...如果您接受显式指定大小...您可以编写一个递归自定义类型特征来构造最终类型。
// declararion and ground case
template <typename T, std::size_t ...>
struct get_ranked_array
 { using type = T; };

// recursive case
template <typename T, std::size_t Dim0, std::size_t ... Dims>
struct get_ranked_array<T, Dim0, Dims...>
   : public get_ranked_array<T[Dim0], Dims...>
 { };

template <typename T, std::size_t ... Dims>
using get_ranked_array_t = typename get_ranked_array<T, Dims...>::type;

而 make 函数只需变成

template <std::size_t ... Dims>
auto make_ranked_array (get_ranked_array_t<float, Dims...> const & values)
   -> array_type<sizeof...(Dims)>
 {
   return {{Dims...},
      std::vector<float>(
         reinterpret_cast<float const *>(values),
         reinterpret_cast<float const *>(values) + (Dims * ...))};
 }

您可以按照以下方式使用它

auto arr1 = make_ranked_array<3u>({1.0f, 2.0f, 3.0f}); 
auto arr2 = make_ranked_array<3u, 2u>({ {1.0f, 2.0f, 3.0f},
                                        {4.0f, 5.0f, 6.0f} }); 

如果你想的话,你可以保持最后一个大小的扣除,但我不知道这是否是一个好主意。

以下是一个完整的编译C++17示例

#include <array>
#include <vector>

template <std::size_t N>
struct array_type
 {
   std::array<std::size_t, N> shape;
   std::vector<float> data;
 };

// declararion and ground case
template <typename T, std::size_t ...>
struct get_ranked_array
 { using type = T; };

// recursive case
template <typename T, std::size_t Dim0, std::size_t ... Dims>
struct get_ranked_array<T, Dim0, Dims...>
   : public get_ranked_array<T[Dim0], Dims...>
 { };

template <typename T, std::size_t ... Dims>
using get_ranked_array_t = typename get_ranked_array<T, Dims...>::type;

template <std::size_t ... Dims>
auto make_ranked_array (get_ranked_array_t<float, Dims...> const & values)
   -> array_type<sizeof...(Dims)>
 {
   return {{Dims...},
      std::vector<float>(
         reinterpret_cast<float const *>(values),
         reinterpret_cast<float const *>(values) + (Dims * ...))};
 }

int main ()
 {
   auto arr1 = make_ranked_array<3u>({1.0f, 2.0f, 3.0f}); 
   auto arr2 = make_ranked_array<3u, 2u>({ {1.0f, 2.0f, 3.0f},
                                           {4.0f, 5.0f, 6.0f} }); 

   static_assert ( std::is_same_v<decltype(arr1), array_type<1>> );
   static_assert ( std::is_same_v<decltype(arr2), array_type<2>> );
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接