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

时间:2018-05-07 06:31:49

标签: c++ templates c++17 template-deduction

我一直在尝试创建一个类,它表示一个非拥有的,多维的数组视图(有点像N维std::string_view),其中维度是多变的"动态&#34 ;。即维度和维度大小的数量与类没有关联,但是在访问元素时指定(通过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;
                           ^

我不是模板实例化/演绎方面的专家。但是在我看来,编译器试图从N参数中推导出std::initializer_list<int>,这会失败,因为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>,因为它在未能推断出函数模板参数后停止。

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

2 个答案:

答案 0 :(得分:2)

  

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

显然,您可以将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;
 }

答案 1 :(得分:1)

无法编译的原因是{0, 0, 0}std::array(称为 braced-init-list )之类的东西不像第一类表达式。他们没有类型。在模板推导中,我们尝试将类型与表达式匹配 - 我们不能对 braced-init-list 执行此操作。即使我们可以, braced-init-list 也不是任何类型的std::initializer_list<T>,因此不匹配。这是我们需要额外语言支持的东西,我们只是没有。

有两个例外。

最重要的是dimensions。但是这具有我们在这种情况下不需要的运行时大小,因为您希望强制indicesT[N]参数具有相同的大小(可能)。

另一个是原始数组。您可以从braced-init-list中推导出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})

这将允许您撰写D,其中IintN3推断为view({5, 4, 3}, {0})。它还可以正确阻止编译view({5}, {0, 0})D

您可能希望添加IonPressItem = (id) => { this.setState((state) => { const selected = selected === id ? null : id; return { selected }; }); }; 为整数类型的其他约束。