在模板参数中,哪些规则允许编译器推断数组的项数?
Posted
技术标签:
【中文标题】在模板参数中,哪些规则允许编译器推断数组的项数?【英文标题】:In a template argument, what rules allow the compiler to infer the number of items of an array? 【发布时间】:2021-04-13 14:42:24 【问题描述】:鉴于下面的两个程序——除了模板函数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]
?
【问题讨论】:
我不打算去寻找参考(因为我觉得这是一个相当众所周知的事实),但是函数参数中的T x[N]
完全,完全等同于T *x
。
... 但T(&x)[N]
不等于T * const (&x)
?!?
不,此规则不适用于递归。它仅适用于数组。 T x[N]
是一个数组,所以它适用于它,T(&x)[N]
是一个引用而不是数组,所以它不适用(对数组的引用,是的,但它的引用无关紧要)。
我想我明白你的意思了。谢谢。
【参考方案1】:
[temp.deduct] [emphasis mine] 涵盖了模板参数推导:
/1 当一个函数模板特化被引用时,所有的 模板参数应该有值。 值可以明确 指定或,在某些情况下,从使用中推断或获得 来自默认模板参数。
/2(...关于显式模板参数列表:此处不相关)
/3 执行完这个替换后,函数参数类型 执行 [dcl.fct] 中描述的调整。 [ 示例:A “void (const int, int[5])”的参数类型变为 “无效()(int,int)”。 —结束示例] [...]
注意/3中对[dcl.fct]和相关(非规范)示例的引用,显示将值类型数组函数参数int[N]
调整为int*
,表示非类型模板参数N
不能从传递给值类型数组参数的参数中推导出来(N
在这种情况下无用/被忽略)。
从函数调用中推导模板参数,特别是,[temp.deduct.call] 涵盖;区分您的两个示例的相关部分是[temp.deduct.call]/2.1,它表示如果调用的参数(表示为A
)是数组类型,并且参数类型(表示为P
)是not 引用类型,指针类型用于类型推导:
/2 如果
(2.1) 如果P
不是引用类型: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)和A
作为单个函数参数的int[3]
应用模板参数推导的len
,推导T
到int
和N
到3
,为参数类型P
的完整类型推导;根据[temp.deduct.type]/1:
模板参数可以在几种不同的上下文中推导出来,但是 在每种情况下,都是根据模板参数指定的类型 (称为
P
)与实际类型(称为A
)进行比较,然后 尝试查找模板参数值(类型的类型 参数,非类型参数的值,或模板 模板参数),这将使P
,在替换后 推导值(称为推导A
),兼容A
。
即非类型模板参数N
是单个函数参数的类型的一部分(类型模板参数T
也是如此)。
【讨论】:
【参考方案2】:在这个:
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
。
【讨论】:
【参考方案3】:第一个相当于一个指针。请参阅 dcl.fct(由 temp.deduct 调用,正如@dfrib 的回答所解释的那样):
函数的类型使用以下规则确定。 ...确定每个参数的类型后,将任何类型为“
T
”的参数...调整为“指向T
”的指针。 ...
这不适用于第二个,因为它是对数组的引用(没有引用数组,但对数组的引用很好)。
【讨论】:
以上是关于在模板参数中,哪些规则允许编译器推断数组的项数?的主要内容,如果未能解决你的问题,请参考以下文章
C++ Primer 5th笔记(chap 16 模板和泛型编程)模板实参推断和引用