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

问题描述

给定下面的两个程序——除了模板函数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,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 的语法糖(您可以在错误消息中看到 - {{1 }})。因此,即使传入了一个固定长度的数组,也没有任何内容可以推导出 std::size_t len(T*)

,

模板参数推导包含在[temp.deduct] [重点我的]中:

/1 当一个函数模板特化被引用时,所有的 模板参数应该有值。 值可以明确 指定或,在某些情况下,从使用中推导出或获得 来自默认模板参数。

/2(...关于显式模板参数列表:此处不相关)

/3 执行此替换后,函数参数类型 执行 [dcl.fct] 中描述的调整。 [ 示例:A “void (const int,int[5])”的参数类型变成 “void()(int,int)”。 — 结束示例 ] [...]

注意 /3 中对 [dcl.fct] 和相关(非规范)示例的引用,显示了将值类型数组函数参数 int[N] 调整为 int*,表示非规范-type 模板参数 N 不能从传递给值类型数组参数的参数推导出(N 在此上下文中无用/被忽略)。

从函数调用中推导出模板参数,特别是[temp.deduct.call];区分您的两个示例的相关部分是 [temp.deduct.call]/2.1,它表示如果调用的参数(表示为 A)是数组类型,并且参数类型(表示为 P不是一个引用类型,一个指针类型用于类型推导:

/2 如果 P 不是引用类型:

  • (2.1) 如果 A 是数组类型,则使用数组到指针标准转换产生的指针类型代替 A 的类型 扣除;否则,[...]

而当 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)和 {{1} 应用模板参数推导} 作为 A 的单个函数参数 int[3],它推导出 lenTintN 的完整类型推导参数类型3;根据{{​​3}}:

可以在几种不同的上下文中推断出模板参数,但是 在每种情况下都是根据模板参数指定的类型 (称为 P)与实际类型(称为 P)进行比较,并且 尝试查找模板参数值(类型的类型 参数、非类型参数的值或模板 模板参数),将在替换后生成 A 推导的值(称之为推导的 P),与 A 兼容。

即,非类型模板参数 A 是单个函数参数的 type 的一部分(类型模板参数 N 也是如此)。 >

,

第一个相当于一个指针。请参阅 [temp.deduct.type]/1(由 dcl.fct 调用,如@dfrib 的回答所述):

函数的类型使用以下规则确定。 ...在确定每个参数的类型后,任何类型为“T数组”的参数都被调整为“指向T的指针”。 ...

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