带有参数列表初始化的模板参数推导

Template argument deduction with parameter list initialisation

我一直在尝试创建一个 class,它表示数组的非拥有的多维视图(有点像 N 维 std::string_view),其中维度是变化的 "dynamically"。即维数和维大小与 class 无关,而是在访问元素时指定(通过 operator())。以下代码总结了我正在寻找的功能:

#include <array>
#include <cstddef>

template<typename T>
struct array_view {

    T* _data;

    // Use of std::array here is not specific, I intend to use my own, but similar in functionality, indices class.
    template<std::size_t N>
    T& operator()(std::array<std::size_t, N> dimensions, std::array<std::size_t, N> indices) const
    {
        std::size_t offset = /* compute the simple offset */;

        return _data[offset];
    }

};

int main()
{
    int arr[3 * 4 * 5] = {0};

    array_view<int> view{arr};

    /* Access element 0. */
    // Should call array_view<int>::operator()<3>(std::array<std::size_t, 3>, std::array<std::size_t, 3>)
    view({5, 4, 3}, {0, 0, 0}) = 1;
}

然而这个fails to compile(忽略operator()中明显的语法错误)与

main.cpp: In function 'int main()':
main.cpp:28:27: error: no match for call to '(array_view<int>) (<brace-enclosed initializer list>, <brace-enclosed initializer list>)'
  view({5, 4, 3}, {0, 0, 0}) = 1;
                           ^
main.cpp:11:5: note: candidate: 'template<long unsigned int N> T& array_view<T>::operator()(std::array<long unsigned int, N>, std::array<long unsigned int, N>) const [with long unsigned int N = N; T = int]'
  T& operator()(std::array<std::size_t, N> dimensions, std::array<std::size_t, N> indices) const
     ^~~~~~~~
main.cpp:11:5: note:   template argument deduction/substitution failed:
main.cpp:28:27: note:   couldn't deduce template parameter 'N'
  view({5, 4, 3}, {0, 0, 0}) = 1;
                           ^

我不是模板方面的专家instantiation/deduction。然而,在我看来,编译器试图从 std::initializer_list<int> 参数中推断出 N,但失败了,因为 operator() 被声明为采用 std::array<std::size_t, N> 参数。因此编译失败。

执行 another, far more simplified, experiment,显示类似的结果:

template<typename T>
struct foo {
    T val;
};

struct bar {
    template<typename T>
    void operator()(foo<T>) {}
};

int main()
{
    bar b;
    b({1});
}

输出:

main.cpp: In function 'int main()':
main.cpp:14:7: error: no match for call to '(bar) (<brace-enclosed initializer list>)'
  b({1});
       ^
main.cpp:8:10: note: candidate: 'template<class T> void bar::operator()(foo<T>)'
     void operator()(foo<T>) {}
          ^~~~~~~~
main.cpp:8:10: note:   template argument deduction/substitution failed:
main.cpp:14:7: note:   couldn't deduce template parameter 'T'
  b({1});

似乎编译器甚至没有尝试将 {1}(这是 foo<int> 的有效初始化)转换为 foo<int>,因为它在推导函数失败后停止模板参数。

那么有什么方法可以实现我正在寻找的功能吗?我是否缺少一些新的语法,或者有相同的替代方法,或者根本不可能吗?

So is there any way to achieve the functionality I'm looking for? Is there some new syntax I'm missing, or an alternate approach that does the same, or is it simply not possible?

显然你可以显式 N 值如下

view.operator()<3U>({{5U, 4U, 3U}}, {{0U, 0U, 0U}}) = 1;

但我知道这是一个丑陋的解决方案。

对于另一种方法...如果您可以接受放弃第二个数组(并使用第二个初始化列表调用运算符)并且如果您可以使用可变参数模板列表...可变参数列表的大小成为幸存数组的维度

template <typename ... Ts>
T & operator() (std::array<std::size_t, sizeof...(Ts)> const & dims,
                Ts const & ... indices) const
 {
    std::size_t offset = 0 /* compute the simple offset */;

    return _data[offset];
 }

您可以按如下方式使用它

view({{5U, 4U, 3U}}, 0U, 0U, 0U) = 1;

显然在运算符内部使用 indices 可能会更复杂,并且可能需要添加一些关于 Ts... 类型的检查(以验证它们是否都可以转换为 std::size_t

但我想你也可以定义一个 func() 方法作为你原来的 operator()

template <std::size_t N>
T & func (std::array<std::size_t, N> const & dims,
          std::array<std::size_t, N> const & inds) const
 {
   std::size_t offset = 0 /* compute the simple offset */;

   return _data[offset];
 }

您可以从 operator()

调用它
template <typename ... Ts>
T & operator() (std::array<std::size_t, sizeof...(Ts)> const & dims,
                Ts const & ... indices) const
 { return func(dims, {{ indices... }}); }

以下是完整的工作示例

#include <array>
#include <cstddef>

template <typename T>
struct array_view
 {
   T * _data;

   template <std::size_t N>
   T & func (std::array<std::size_t, N> const & dims,
             std::array<std::size_t, N> const & inds) const
    {
      std::size_t offset = 0 /* compute the simple offset */;

      return _data[offset];
    }


   template <typename ... Ts>
   T & operator() (std::array<std::size_t, sizeof...(Ts)> const & dims,
                   Ts const & ... indices) const
    { return func(dims, {{ indices... }}); }

 };

int main ()
 {
   int arr[3 * 4 * 5] = {0};

   array_view<int> view{arr};

   view({{5U, 4U, 3U}}, 0U, 0U, 0U) = 1;
 }

它编译失败的原因是 {5, 4, 3}{0, 0, 0}(称为 braced-init-lists)之类的东西不像 first- class 表达式。他们没有类型。在模板推导中,我们尝试将类型与表达式相匹配——但我们不能针对 braced-init-list 进行匹配。即使我们可以,braced-init-list 也不是任何类型的 std::array,因此不会匹配。这是我们需要额外语言支持的东西,我们只是没有。

有两个例外。

大的是std::initializer_list<T>。但是在这种情况下,它具有我们不想要的运行时大小,因为您想强制 dimensionsindices 参数具有相同的大小(大概)。

另一个是原始数组。您可以从花括号初始化列表中推断出 T[N]。所以你可以这样写:

template <typename D, typename I, std::size_t N>
T& operator()(D const (&dimensions)[N], I const (&indices)[N]) const;

这会让你写 view({5, 4, 3}, {0, 0, 0}),这会把 DI 推导为 int,将 N 推导为 3。它还会正确地阻止 view({5, 4, 3}, {0})view({5}, {0, 0}) 编译。

您可能想要添加额外的约束条件,即 DI 是整数类型。