使用 std::size 来自非 const 上下文

Using std::size from non-const context

本文关键字:const 上下文 std size 使用      更新时间:2023-10-16

我想知道为什么constexpr函数(特别是std::size(在只有类型很重要时不能在某些非常量上下文中工作。

让我们看一下两个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(以下是 GCC-8std::size实现(
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除了打字之外什么都不在乎?难道不应该重新实施吗?

我为此写了一整篇博客文章。不,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的值以执行该取消引用。但是new_A不是一个常量,所以我们无法在常量评估期间读取它的值,所以我们已经完成了。我们甚至不需要我们正在阅读的值并不重要,在这种情况下,我们只关心它的类型。

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