有人能解释一下这个模板代码如何给出数组的大小吗?

67
template<typename T, size_t n>
size_t array_size(const T (&)[n])
{
    return n;
}

我不明白的是这个模板函数的参数。当我将数组传递到该函数时,会发生什么使得n成为数组元素的数量?
4个回答

103

首先,您需要了解尝试从数组中获取值可能会给您一个指向其第一个元素的指针:

int a[] = {1, 2, 3};
int *ap = a; // a pointer, size is lost
int (&ar)[3] = a; // a reference to the array, size is not lost

引用是使用其精确类型或基类类型来引用对象。关键在于模板通过引用接受数组。在C++中不存在将数组(而不是对它们的引用)作为参数的情况。如果您给定一个数组类型的参数,它将成为指针。因此,当我们想要知道传递的数组的大小时,使用引用是必要的。大小和元素类型会自动推导,这通常适用于函数模板。以下是模板示例:

template<typename T, size_t n>
size_t array_size(const T (&)[n]) {
    return n;
}

使用之前定义的数组a调用将隐式实例化以下函数:

size_t array_size(const int (&)[3]) {
    return 3;
}

这可以像这样使用:

size_t size_of_a = array_size(a);

我之前想出了一种变化,可以在编译时确定一个值。它不会直接返回该值,而是根据n给模板赋予一个返回类型。

[编辑:事实证明,有人已经在此处提出了同样的想法]

template<typename T, size_t n>
char (& array_size(const T (&)[n]) )[n];

如果数组有n个元素,则返回类型是大小为n且元素类型为char的数组引用。现在,您可以在编译时确定传递的数组大小:
size_t size_of_a = sizeof(array_size(a));

因为一个拥有n个元素的char数组具有n的大小,这也将给出给定数组中元素的数量。在编译时,所以你可以这样做:

int havingSameSize[sizeof(array_size(a))];

由于该函数从未被调用,因此不需要定义,因此它没有主体。希望我能够澄清一点。


谢谢litb,我刚刚几分钟前也发布了同样的问题 :) 对不起重复了。 - Khaled Alshaya
1
我很高兴你找到了答案。不用担心重复,你不可能知道的 :) - Johannes Schaub - litb
使用适当的返回类型的后一种变体的缺点是:它无法与Microsoft Visual Studio 6配合使用(该软件在模板方面相当差,并在此情况下失败)。不幸的是,这对于某些商店来说是一个致命问题(比如我所在的那家商店,必须支持MSVC6)。 - Frerich Raabe
18
有了C++11的constexpr,函数return n;可以成为编译时常量! template <typename T, size_t n> constexpr size_t array_size(const T (&)[n]) { return n; } 该函数可以计算数组的大小。 - legends2k
模板<typename T,size_t n> char(&array_size(const T(&)[n]))[n]; 并调用 sizeof(array_size(a)); 这将返回数组的总大小= N * char的大小,而不是返回N-元素的数量吗? - user6952310

25

从这个角度来看,假设你有一堆函数:

// Note that you don't need to name the array, since you don't
// actually reference the parameter at all.
size_t array_size(const int (&)[1])
{
    return 1;
}

size_t array_size(const int (&)[2])
{
    return 2;
}

size_t array_size(const int (&)[3])
{
    return 3;
}
// etc...

现在当你调用它时,哪个函数会被调用?
int a[2];
array_size(a);  

现在,如果您将数组大小的模板化,则会得到以下结果:
template <int n>
size_t array_size(const int (&)[n])
{
    return n;
}

编译器将尝试实例化与您调用它的参数匹配的array_size版本。因此,如果您使用10个int数组调用它,则将使用n = 10实例化array_size。
接下来,只需将类型模板化,以便您可以使用更多不仅限于int数组的类型进行调用:
template <typename T, int n>
size_t array_size(const T (&)[n])
{
    return n;
}

完成了。

编辑: 关于 (&) 的说明

括号是需要的,用来区分int引用数组(不合法)和整数数组的引用(您想要的内容)。由于 [] 的优先级高于 &,因此如果您有以下声明:

const int &a[1];

由于运算符优先级的影响,你最终得到了一个只有一个元素的 const 引用 int 数组。如果你想要先应用 &,你需要使用括号来强制改变优先级:
const int (&a)[1];  

现在你有一个整型数组的常量引用,只包含一个元素。在函数参数列表中,如果你不使用某个参数,可以省略它的名称,但要保留括号:

size_t array_size(const int (&)[1])

为什么在模板函数版本中需要在&周围加上括号,而在上面的例子中硬编码数组大小时不需要? - Alex
2
你实际上在两种情况下都需要使用括号。 - Eclipse

1

数组不会发生任何事情。它是一个未使用的参数,用于解决模板函数签名。

它也不能用作模板参数,但这是一个单独的细节。


0
对于那些没有 "constexpr" 的人来说,一种有点奇怪的方法是以编译时常量的形式获取结果。
#include <iostream>

namespace
{

    template <size_t V>
    struct helper
    {
        enum
        {
            value = V
        };
    };


    template<typename T, size_t Size>
    auto get_size(T(&)[Size]) -> helper < Size >
    {
        return helper < Size >() ;
    }

    template<typename T>
    struct get_value
    {
        enum
        {
            value = T::value
        };
    };

}

int main()
{
    std::cout << get_value<decltype(get_size("Foo bar baz"))>::value;
}

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