如何解决在模板参数中,哪些规则允许编译器推断数组的项数?
给定下面的两个程序——除了模板函数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]
,它推导出 len
到 T
和 int
到 N
的完整类型推导参数类型3
;根据{{3}}:
可以在几种不同的上下文中推断出模板参数,但是
在每种情况下都是根据模板参数指定的类型
(称为 P
)与实际类型(称为 P
)进行比较,并且
尝试查找模板参数值(类型的类型
参数、非类型参数的值或模板
模板参数),将在替换后生成 A
推导的值(称之为推导的 P
),与 A
兼容。
即,非类型模板参数 A
是单个函数参数的 type 的一部分(类型模板参数 N
也是如此)。 >
第一个相当于一个指针。请参阅 [temp.deduct.type]/1(由 dcl.fct 调用,如@dfrib 的回答所述):
函数的类型使用以下规则确定。 ...在确定每个参数的类型后,任何类型为“T
数组”的参数都被调整为“指向T
的指针”。 ...
这不适用于第二个,因为它是对数组的引用(没有引用数组,但对数组的引用很好)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。