在模板参数中,哪些规则允许编译器推断数组的项数?

In a template argument, what rules allow the compiler to infer the number of items of an array?

给定以下两个程序——除了模板函数的定义外完全相同 len():

// ================================
//  a.cc
// ================================
#include <iostream>

template<class T, std::size_t n>
std::size_t len(T (&v)[n]) { return n; }

int main()
{
    int arr[] = { 1,2,3 };

    std::cout << len(arr) << std::endl;
}
// ================================
//  b.cc
// ================================
#include <iostream>

template<class T, std::size_t n>
std::size_t len(T v[n]) { return n; }

int main()
{
    int arr[] = { 1,2,3 };

    std::cout << len(arr) << std::endl;
}

使用 g++ 时,第一个程序按预期编译和运行。但是第二个不编译。这是诊断:

b.cc: In function ‘int main()’:
b.cc:13:25: error: no matching function for call to ‘len(int [3])’
     std::cout << len(arr) << std::endl;
                         ^
b.cc:7:13: note: candidate: template<class T, long unsigned int n> std::size_t len(T*)
 std::size_t len(T v[n]) { return n; }
             ^
b.cc:7:13: note:   template argument deduction/substitution failed:
b.cc:13:25: note:   couldn't deduce template parameter ‘n’
     std::cout << len(arr) << std::endl;

为什么编译器能够在第一种情况下推断出数组中的项目数,而在第二种情况下却不能?在 C++11 或 C++17 标准中,是什么使得必须编写 T (&v)[n] 而不是 T v[n]

在此:

template<class T, std::size_t n>
std::size_t len(T (&v)[n]) { return n; }

v 是对 T[n] 数组的 引用 。数组的大小 (n) 是数组类型的一部分。所以 n 可以从传入的任何固定大小的数组中推导出来。

在此:

template<class T, std::size_t n>
std::size_t len(T v[n]) { return n; }

在函数参数 T v[n] 中,n 被忽略,而 T v[] 只是 T *v 的语法糖(您可以在错误消息中看到 - std::size_t len(T*)).因此,即使传入了固定长度的数组,也没有什么可以有效地推断出 n

模板参数推导由[temp.deduct][强调我的]:

/1 When a function template specialization is referenced, all of the template arguments shall have values. The values can be explicitly specified or, in some cases, be deduced from the use or obtained from default template-arguments.

/2 (... regarding explicit template argument list: not relevant here)

/3 After this substitution is performed, the function parameter type adjustments described in [dcl.fct] are performed. [ Example: A parameter type of “void (const int, int[5])” becomes “void()(int,int)”. — end example ] [...]

注意对[dcl.fct]的引用和/3中的相关(非规范)示例,显示了值类型数组函数参数int[N]int*的调整,这意味着非类型模板参数 N 不能从传递给值类型数组参数的参数中推断出来(N 在这种情况下是 useless/ignored)。

函数调用的模板参数推导,特别是在 [temp.deduct.call]; and the relevant section that differentiates your two examples is [temp.deduct.call]/2.1 中,它表示如果调用的参数(表示为 A)是数组类型,并且参数类型(表示为P)是不是引用类型,指针类型用于类型推导:

/2 If P is not a reference type:

  • (2.1) If A is an array type, the pointer type produced by the array-to-pointer standard conversion is used in place of A for type deduction; otherwise, [...]

而当 P 引用类型时,如下例所示:

template<class T, std::size_t n>
std::size_t len(T (&v)[n]) { return n; }

[temp.deduct.call]/2.1 不适用,并且对于以下调用名为 len 并以数组作为参数的函数

int arr[] = { 1,2,3 };
(void)len(arr);

名称查找将找到函数模板 len,随后将使用 P 作为 T[N] 应用模板参数推导(根据 [temp.deduct.call]/3) and A as int[3] for the single function parameter of len, which deduces T to int and N to 3 for the complete type deduction of the parameter type P; as per [temp.deduct.type]/1:

Template arguments can be deduced in several different contexts, but in each case a type that is specified in terms of template parameters (call it P) is compared with an actual type (call it A), and an attempt is made to find template argument values (a type for a type parameter, a value for a non-type parameter, or a template for a template parameter) that will make P, after substitution of the deduced values (call it the deduced A), compatible with A.

即非类型模板参数N是单函数参数类型的一部分(类型模板参数[=29=也是如此) ]).

第一个相当于指针。请参阅 dcl.fct (invoked by temp.deduct 作为@dfrib 的回答解释):

The type of a function is determined using the following rules. ... After determining the type of each parameter, any parameter of type “array of T” ... is adjusted to be “pointer to T”. ...

这不适用于第二个,因为它是对数组的引用(没有引用数组,但对数组的引用很好)。