在非const上下文中使用std::size

4

我想知道为什么constexpr函数(特别是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))

自C++11起,你可以使用constexpr(以下是来自GCC-8的std::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除了类型之外还关心其他东西?难道它不应该被重新实现吗?
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的值才能执行该取消引用。但是new_A不是一个常量,因此我们无法在常量求值期间读取它的值,所以我们已经完成了。我们不关心我们正在读取的值,只关心它在这种情况下的类型。
这目前是一种根本性限制,在静态大小的范围内,您需要使用类型特征(或宏)将其大小作为常量表达式获取,而在动态大小的范围内,则需要依赖于std::size()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接