为什么`std :: array :: at()`没有实现为模板函数?

时间:2018-01-06 03:35:22

标签: c++ c++11 c++14

编辑:我忘了提到这将是constexpr上下文,其中没有任何动态索引。

考虑以下(非常天真)的实现:

template <class T, std::size_t N>
class array
{
    // ...

    template <size_type pos>
    reference at()
    {
        static_assert(pos < N, "Index out of range.");
        return m_data[pos];
    }
}

具有以下用途:

int main()
{
    array<int, 5> a{1, 2, 3, 4, 5};

    cout << a.at(10) << "\n";   // will throw at runtime

    cout << a.at<10>() << "\n"; // static assert error; index out of range
    return 0;
}

这有效地防止了类型的超出范围访问以及将引发的任何讨厌的段错误或异常。编译器会抛出一个错误,如下所示:

error: static assertion failed: Index out of range
             static_assert(pos < N, "Index out of range");

大多数IDE会捕获错误的访问权限。那么为什么没有这样实现呢?

注意:我很抱歉,如果这是显而易见的,但我计划编写自己的array课程以提高性能和安全性,这个想法突然出现在我的脑海中头。

5 个答案:

答案 0 :(得分:5)

您需要能够动态访问元素。仅因为在编译时已知大小并不意味着在编译时已知访问的索引。考虑a.at(i)其中i是用户输入。

此外,at函数需要按标准执行运行时检查,请参阅[sequence.reqmts]

  

成员函数at()提供对容器元素的边界检查访问。如果at() out_­of_­range,则n >= a.size()会抛出at

此外,从C ++ 17开始,#include <array> int main() { constexpr std::array<int,5> a = { 1, 2, 3, 4, 5 }; constexpr int b = a.at(2); constexpr int c = a.at(10); } 成员函数被标记为constexpr,因此对于在编译时保持不变的索引,您的问题中的模板化函数没有区别。

at

Live on Wandbox (错误信息是meh ......)

如果您在编译时知道索引并且不想支付std::get<10>(a)在C ++ 17之前产生的额外费用,则可以使用#include <array> int test(std::array<int,5> const &a) { return std::get<1>(a); } 。此变体具有零开销,因为编译器可以完全内联数组访问

at()

Live on Godbolt

答案 1 :(得分:2)

您可以将std::get用于此目的:

#include <array>
#include <iostream>

int main() {
    constexpr std::array<int, 4> foo { 1, 2, 3, 4 };
    std::cout << std::get<2>(foo) << std::endl;
}

另请注意,operator[]at都是constexpr

constexpr std::array<int, 4> foo { 1, 2, 3, 4 };
std::cout << std::get<foo[2]>(foo) << std::endl;
std::cout << std::get<foo.at(2)>(foo) << std::endl;

评估为std::get<3>(foo)(评估为4,因为它也是constexpr)。

如果您尝试使用constexpr中数组边界之外的索引,编译已经失败。

最后,正如多人已经指出的那样,数组的一个主要好处是你可以从中读取动态索引。 IMO,你夸大了静态索引的有用性。

答案 2 :(得分:1)

  

那为什么没有这样实现呢?

因为以你的方式只能使用编译时已知的值。

我的意思是......将for ( auto i = 0U ; i < 4U ; ++i ) std::cout << a.at(i) << std::endl; 定义为接收参数(不是模板),您可以编写

for ( auto i = 0U ; i < 4U ; ++i )
   std::cout << a.at<i>() << std::endl;

但你不能写

i

因为at()已知(固定)编译时间。

- 编辑 -

OP写

  

我真正想要的是在编译时强制执行绑定检查

如果您按以下方式编写constexpr T & at (std::size_t pos) { return pos < N ? m_data[pos] : throw std::range_error("out of range"); }

constexpr

你得到一个绑定检查编译时间,当方法是{{1}}执行编译时,否则运行时。

答案 3 :(得分:0)

显然,我们不希望只有 索引是编译时常量。如果你问“为什么不有一个函数模板用于使用常量表达式索引进行安全访问?”,我只能说用例似乎不常见(与{{1}不同)并且客户端可以自己实现它,没有任何困难或性能损失(这是包含在标准库中的通常标准之一)。

评论:即使在std::tuple函数中,也不能使用普通函数参数作为模板参数。

答案 4 :(得分:0)

除了已经指出at必须采用运行时参数和std :: get作为替代方案之外:如果在constexpr上下文中使用operator[]并超出界限,您将会在你想要的时候在C ++ 14中得到一个编译时错误,只要你在const数组上调用它:

const std::array<int, 5> a{};
constexpr int v = a[10];

gcc给出错误:

error: array subscript value ‘10’ is outside the bounds of array type ‘std::__array_traits<int, 5>::_Type {aka int [5]}’
   constexpr int v = a[10];