使用 std::size 来自非 const 上下文
Using std::size from non-const context
我想知道为什么constexpr函数(特别是std::size(在只有类型很重要时不能在某些非常量上下文中工作。
让我们看一下两个array_size实现:
- 老好 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
除了打字之外什么都不在乎?难道不应该重新实施吗?
我为此写了一整篇博客文章。不,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()
。