从非const上下文中使用std::size。

问题描述 投票:2回答:1

我想知道为什么conexpr函数(尤其是std::size)在一些非const上下文中不能工作,而只有类型很重要。

让我们来看看两个array_size的实现。

  1. 老式的c++98
template <typename T, size_t N>
char (&array_size_helper(const T (&)[N]))[N];
#define array_size(a)  sizeof(array_size_helper(a))
  1. 从C++11开始,你可以使用constexpr(以下是 std::size (GCC-8的实施)
constexpr size_t size(const _Tp (&/*__array*/)[_Nm]) noexcept { return _Nm; }

第二个版本很好很完美,只是和第一个版本的工作方式不一样。由于第一个宏与 sizeof 它只关心类型,而 constexpr 函数是很复杂的事情。

考虑一个例子。

struct A
{
    int a[10];
};

template <typename T, size_t N>
char (&array_size_helper(const T (&)[N]))[N];

# define array_size(a)  sizeof(array_size_helper(a))

int main()
{
    A a;
    A* new_A = reinterpret_cast<A*>(&a);
    static_assert(array_size(a.a) == 10) // OK;
    static_assert(array_size(new_A->a) == 10); //OK
    static_assert(std::size(a.a) == 10); //OK
    static_assert(std::size(new_A->a) == 10); //error: the value of ‘new_A’ is not usable in a constant expression
}

为什么会这样?为什么 std::size 除了类型,还在乎什么?是不是应该重新实现?

c++ c++14 c++17
1个回答
3
投票

我写了一个 整篇博文 关于这个。不知道 std::size 不应该被重新实现。

这两种实现之间有一个重要的区别:在使用 array_size(),所有的东西都是在一个未评估的上下文中。只有类型是重要的,而不是任何特定的值。array_size() 适用于任何 C 数组类型,而不适用其他类型。

std::size() 另一方面,它适用于所有的范围。但它 来评估它的参数。而当我们在做恒定的评估时,我们有一套严格的规则必须遵循。其中一条就是未定义的行为是不规范的--编译器必须跟踪每一次这样的访问。所以当你读过一个指针或引用时,编译器必须验证这个读是有效的。这是很奇怪的表情 std::size(a.a) 工作但 std::size(new_A->a) 没有,但要考虑这两种情况下必须发生的不同操作。

  • 对于 std::size(a.a),我们永远不会有看看 a. 成员访问只是一些偏移量。我们将一个引用捆绑到这个偏移量上(参数为 std::size),但实施 size() 其实从来没有读过这个提法。所以即使 a 本身并不 可读 在一个常量表达式中,我们实际上并没有对它进行任何读取--所以这只是工作。
  • 对于 std::size(new_A->a)的值,我们首先要做的是读取 new_A 以便执行该dereference。但是 new_A 不是一个常量,所以我们不能在常量评估时读取它的值,所以我们已经完成了。这并不重要,我们甚至不需要我们读到的值,在这种情况下我们只关心它的类型。

这是目前的一个基本限制--在静态大小的范围内,你需要一个类型特征(或宏)来获取它们作为常量表达式的大小,而动态大小的范围,你需要依靠 std::size().

© www.soinside.com 2019 - 2024. All rights reserved.