【问题标题】:Why is non-const std::array::operator[] not constexpr?为什么非常量 std::array::operator[] 不是 constexpr?
【发布时间】:2016-03-15 23:07:35
【问题描述】:

我正在尝试使用给定函数在编译时填充二维数组。这是我的代码:

template<int H, int W>
struct Table
{
  int data[H][W];
  //std::array<std::array<int, H>, W> data;  // This does not work

  constexpr Table() : data{}
  {
    for (int i = 0; i < H; ++i)
      for (int j = 0; j < W; ++j)
        data[i][j] = i * 10 + j;  // This does not work with std::array
  }
};

constexpr Table<3, 5> table;  // I have table.data properly populated at compile time

它工作得很好,table.data 在编译时被正确填充。

但是,如果我将纯二维数组 int[H][W] 更改为 std::array&lt;std::array&lt;int, H&gt;, W&gt;,我会在循环体中出现错误:

error: call to non-constexpr function 'std::array<_Tp, _Nm>::value_type& std::array<_Tp, _Nm>::operator[](std::array<_Tp, _Nm>::size_type) [with _Tp = int; long unsigned int _Nm = 3ul; std::array<_Tp, _Nm>::reference = int&; std::array<_Tp, _Nm>::value_type = int; std::array<_Tp, _Nm>::size_type = long unsigned int]'
data[i][j] = i * 10 + j;
^
Compilation failed

显然,我试图调用std::array::operator[] 的非常量重载,而不是constexpr。问题是,为什么不是constexpr?如果 C++14 允许我们修改在 constexpr 范围内声明的变量,为什么 std::array 不支持?

我曾经认为std::array 就像普通数组一样,只是更好。但这里有一个例子,我可以使用普通数组,但不能使用std::array

【问题讨论】:

  • 看起来像是标准中的疏忽。
  • 一旦您使用索引 std::integer_sequence 习惯用法定义了从 C 样式数组到 std::array 的 constexpr 转换,它对 C++14 中的 constexpr 元编程非常有帮助。

标签: c++ arrays c++14 constexpr


【解决方案1】:

好的,这确实是标准的疏忽。甚至有解决此问题的建议:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0107r0.pdf

[N3598] 移除了将 constexpr 成员函数隐式标记为 const。然而 std::array 的成员函数在此更改后未重新访问,导致令人惊讶的缺失 在 std::array 的接口中支持 constexpr。本文通过添加来修复这个遗漏 constexpr 到 std::array 的成员函数,可以用最少的数量支持它 工作。

UPD:在 C++17 中已修复:https://en.cppreference.com/w/cpp/container/array/operator_at

【讨论】:

  • 糟糕。 (“对标准的监督”是最糟糕的监督类型之一。呃。他们难道不是每天都在验证这些事情吗?)
  • @JeffY 好吧,C++ 委员会主要由志愿者(尽管是一流的专业人员)组成,他们一年只有几次会议(参见isocpp.org/std/meetings-and-participation/upcoming-meetings)。他们必须决定很多事情(例如,参见 Herb Sutter 的现场报告:herbsutter.com/2015/10/25/2568)。在std::make_unique() 不是 C++11 的一部分之后,我现在并不感到惊讶 :)
【解决方案2】:

std::array::operator[] 因为 C++14 是 constexpr 但也是 const 合格的:

constexpr const_reference operator[]( size_type pos ) const;
                                                      ^^^^^

因此,您必须强制转换数组以调用正确的 operator[] 重载:

template<int H, int W>
struct Table
{
  //int data[H][W];
  std::array<std::array<int, H>, W> data;  // This does not work

  constexpr Table() : data{} {
    for (int i = 0; i < W; ++i)
      for (int j = 0; j < H; ++j)
        const_cast<int&>(static_cast<std::array<int, H> const&>(static_cast<std::array<std::array<int, H>, W> const&>(data)[i])[j]) = 10 + j;
  }
};

Live Demo

编辑:

与某些人相反,以这种方式使用const_cast 并不意味着未定义的行为。事实上,正如在放松 constexpr 的提案中所建议的那样,用户需要使用 const_cast 来解决此问题,以便至少在 C++ 中解决问题之前引发正确的下标运算符重载17 (see link)。

【讨论】:

  • 感谢工作示例,即使它没有回答我的问题。看起来像黑客,你不觉得吗?
  • @101010 我会怀疑并说他的意思是“是 UB,还是不是?”而不是“你能解释一下吗?”。
  • @luk32 这不是 UB,为澄清而给 Shafik Yaghmour 的版税。 stackoverflow.com/a/34341498/2352671
  • 事实上,会导致 UB 的操作在constexpr 上下文中被明确禁止(即至少需要诊断),所以我们可以在多个编译器上检查这一点,如果它编译,它不是 UB — 除非所有检查过的编译器都坏了。
【解决方案3】:

虽然我的第一个想法是“为什么在非常量数组上需要一个 constexpr 方法”? ...

然后我坐下来写了一个小测试,看看这个想法是否有意义:

#include <iostream>

using namespace std;
struct X{

    constexpr X()
    : _p { 0, 1, 2, 3, 4, 5, 6, 7, 9 }
    {
    }

    constexpr int& operator[](size_t i)
    {
        return _p[i];
    }

    int _p[10];
};

constexpr int foo()
{
    X x;
    x[3] = 4;
    return x[3];
}


auto main() -> int
{
    cout << foo() << endl;

    return 0;
}

事实证明确实如此。

因此,我得出的结论是,委员会采取了与我相同的“明显”观点,并打消了这个想法。

在我看来,好像可以向委员会提出建议以在 c++17 中对其进行更改 - 以这个问题为例。

【讨论】:

  • 有道理。你能建议一个人联系以提出建议吗? :) PS 你不是说return x[3]吗?
  • @Mikhail 我选择返回一个不同的下标,以防优化器认为返回 x[3] 是一个 nop :) 每当我在一个文件中编写这些小测试时,我都会尝试预先-empt 优化器决定整个程序可以被优化掉!
【解决方案4】:

这个问题让我非常感兴趣,以至于我决定找出一个解决方案,允许在编译时使用一个以 x 和 y 作为参数的函数来初始化数组。

大概这可以适用于任意数量的维度。

#include <iostream>
#include <utility>


// function object that turns x and y into some output value. this is the primary predicate
struct init_cell_xy
{
    constexpr init_cell_xy() = default;

    constexpr int operator()(int x, int y) const
    {
        return (1 + x) * (1 + y);
    }
};

// function object that applies y to a given x
template<int X = 1>
struct init_cell_for_x
{
    constexpr init_cell_for_x() = default;

    constexpr int operator()(int y) const
    {
        return _xy(X, y);
    }

private:
    init_cell_xy _xy;
};

// an array of dimension 1, initialised at compile time
template<int Extent>
struct array1
{
    template<class F, int...Is>
    constexpr array1(F&& f, std::integer_sequence<int, Is...>)
    : _values { f(Is)... }
    {}

    template<class F>
    constexpr array1(F&& f = init_cell_for_x<>())
    : array1(std::forward<F>(f), std::make_integer_sequence<int, Extent>())
    {}

    constexpr auto begin() const { return std::begin(_values); }
    constexpr auto end() const { return std::end(_values); }
    constexpr auto& operator[](size_t i) const {
        return _values[i];
    }

private:
    int _values[Extent];

    friend std::ostream& operator<<(std::ostream& os, const array1& s)
    {
        os << "[";
        auto sep = " ";
        for (const auto& i : s) {
            os << sep << i;
            sep = ", ";
        }
        return os << " ]";
    }
};

// an array of dimension 2 - initialised at compile time
template<int XExtent, int YExtent>
struct array2
{
    template<int...Is>
    constexpr array2(std::integer_sequence<int, Is...>)
    : _xs { array1<YExtent>(init_cell_for_x<Is>())... }
    {}

    constexpr array2()
    : array2(std::make_integer_sequence<int, XExtent>())
    {}

    constexpr auto begin() const { return std::begin(_xs); }
    constexpr auto end() const { return std::end(_xs); }
    constexpr auto& operator[](size_t i) const {
        return _xs[i];
    }

private:
    array1<YExtent> _xs[XExtent];

    friend std::ostream& operator<<(std::ostream& os, const array2& s)
    {
        os << "[";
        auto sep = " ";
        for (const auto& i : s) {
            os << sep << i;
            sep = ",\n  ";
        }
        return os << " ]";
    }

};




auto main() -> int
{
    using namespace std;

    constexpr array2<6,6> a;

    cout << a << endl;
    return 0;
}

【讨论】:

    猜你喜欢
    • 2015-10-19
    • 1970-01-01
    • 2020-10-30
    • 1970-01-01
    • 2016-05-01
    • 2015-12-14
    • 2013-09-21
    • 2019-04-02
    • 2021-05-07
    相关资源
    最近更新 更多