我有constexpr std::array<int, N> v1{0}, v2{0};
,其行为类似于大整数。因此,我编写了一个multiply
函数来查找数字的乘积。
#include <array>
#include <cstdint>
using namespace std;
template <int N>
constexpr array<int, 2 * N> multiply(const array<int, N> &a,
const array<int, N> &b) {
const int as = N, bs = N, rs = as + bs;
array<int, rs> result{0};
__int128_t carry = 0;
auto pr = begin(result);
for (int r = 0, lim = min(rs, as + bs - 1); r < lim; ++r) {
int i = r >= as ? as - 1 : r,
j = r - i,
k = i < bs - j ? i + 1 : bs - j; // min(i+1, bs-j);
auto pa = begin(a) + i;
auto pb = begin(b) + j;
while (k--) {
carry += static_cast<__int128_t>(*(pa--)) * (*(pb++));
}
*(pr++) = static_cast<int64_t>(carry);
}
return result;
}
int main() {
constexpr int N = 20;
constexpr array<int, N> v1{0}, v2{0};
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
return result[1];
}
请注意,乘法函数将其最小化是不正确的。
当我使用clang++ -std=c++17 -Wall -O0 example.cc
编译此代码时,我错误地得到:
example.cc:30:32: error: constexpr variable 'result' must be initialized by a constant expression
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
^ ~~~~~~~~~~~~~~~~~~~
example.cc:20:50: note: cannot refer to element -1 of array of 20 elements in a constant expression
carry += static_cast<__int128_t>(*(pa--)) * (*(pb++));
^
example.cc:30:41: note: in call to 'multiply(v1, v2)'
constexpr array<int, 2 *N> result = multiply<N>(v1, v2);
^
1 error generated.
但是这可以用gcc正确编译。
为什么我认为clang的错误是一个错误:
为了验证是否存在访问权限超出限制,我启用了libstdc ++的调试模式,并使用g++ -std=c++17 -Wall -D_GLIBCXX_DEBUG -g -O0 example.cc
进行了编译,如果访问权限超出限制,则不会发生崩溃。
同样在消毒剂(g++ -fsanitize=address,undefined -fno-omit-frame-pointer
)下运行成功。
我很好奇为什么clang声称超出了访问范围,而实验清楚地表明并非如此。
C是对的。您没有“ erroneously get”错误。
*(pa--)
假设k
最初设置为i + 1
,在最后一次在while
循环中最后一次对该表达式求值之前,pa
指向数组的第一个元素。 pa--
涉及评估pa - 1
,根据[expr.add]/4会导致未定义的行为:(i = 0
,j = 1
)
当将具有整数类型的表达式添加到指针或从指针中减去时,结果将具有指针操作数的类型。如果表达式
P
指向具有x[i]
元素的数组对象x
的元素n
,则表达式P + J
和J + P
(其中J
的值为j
)指向(如果x[i + j]
,则可能是假设的)元素0 ≤ i + j ≤ n
;否则,行为是不确定的。 [同样,如果P - J
,则表达式x[i − j]
指向(可能是假设的)元素0 ≤ i − j ≤ n
;否则,行为是不确定的。
现在,常量表达式不能求值:[expr.const]/2.6
将具有本国际标准第[intro]至[cpp]节中指定的不确定行为的操作
因此,multiply<N>(v1, v2)
不是常数表达式,并且在这里需要诊断。
当然,除非您取消引用它,否则[-1]
指针通常不会引起问题,但是它仍然是未定义的行为,这阻止了它成为常量表达式的一部分。消毒剂只能诊断出未定义行为的有限子集。