• 文章
  • 从数组中推导模板参数
作者:
发布于 2011 年 8 月 26 日 (最后更新于 2011 年 8 月 26 日)

从数组维度推导模板参数

评分:4.0/5 (79 票)
*****
C++ 的模板机制不仅允许您使用类型(如 std::vector<int> 中的 int)进行参数化,还可以使用值进行参数化。非类型模板参数可以是以下类型[1]
  • 整型(或枚举)值
  • 对象/函数指针
  • 对象/函数引用
  • 成员指针

我将探讨其中第一种类型——整数——以及模板参数推导如何处理数组。

模板参数推导是指在模板参数未指定时,编译器如何确定实例化模板的机制,例如:
1
2
std::vector<int> vi;
std::sort(vi.begin(), vi.end());

尽管我们没有为 std::sort() 指定要使用的迭代器类型,但编译器会根据我们提供的参数进行推导。

数组维度作为模板参数

我们可以创建一个以数组维度为模板的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <string>

template<int N>
void fun(std::string s[N])
{
   for (int i(0); i < N; ++i)
      std::cout << i << ": " << s[i] << std::endl;
}

int main()
{
   std::string s[2] = {"hello", "world"};
   fun<2>(s);
}

$> ./a.out
0: hello
1: world

请注意,在此实现中省略显式模板参数,改用 fun(s) 调用,将会导致编译错误。
$> g++ broken.cpp 
broken.cpp: In function ‘int main()’:
broken.cpp:14:9: error: no matching function for call to ‘fun(std::string [2])’

这让我困惑了一段时间,因为我一直认为模板参数可以从数组维度推导出来。

(注:顺便说一句,如果您写成 fun<500>(s) 也能正常工作;我认为这是因为数组会退化为指针,然后指针可以轻松地初始化数组参数。)

从数组维度推导模板参数

Stroustrup 的 TCPL 说道[2],“编译器可以从类型为...type[I] 的函数模板参数中推导出非类型模板参数 I”,这对我来说意味着上述情况应该能够正常工作。
我花了些时间琢磨为什么参数不能被推导出来,最终找到了答案。标准规定,类型为“N T 的数组”(例如,“5 个 int 的数组”)的值可以转换为类型为“T 的指针”的右值。[3] 这意味着数组的大小在实例化过程中丢失了,因此 N 的值无法推导,模板实例化失败,并且——在我们上面的例子中——无法解析 fun()

防止这种转换(称为“退化”)的方法是将函数参数声明为数组的引用,方法是将 fun(string s[N]) 改为 fun(string (&s)[N])
1
2
3
4
5
6
7
8
9
10
11
12
template<int N>
void fun(string (&s)[N])
{
   for (int i(0); i < N; ++i)
      cout << i << ": " << s[i] << endl;
}

int main()
{
   string s[2] = {"hello", "world"};
   fun(s);
}

这样就能正常工作了!


多维数组

有趣的是,虽然在这个多维数组的替代实现中我没有声明数组的引用,但它仍然正常工作。
1
2
3
4
5
6
7
8
9
10
11
12
template<int N>
void fun(string s[1][N])
{
   for (int i(0); i < N; ++i)
      cout << i << ": " << s[0][i] << endl;
}

int main()
{
   string s[1][2] = {{"hello", "world"}};
   fun(s);
}


原因是数组退化不会递归发生。因此,在调用 fun() 时,int[1][2] 退化为指向 2 个 int 数组的指针,仅此而已,之后不再退化,因此仍然保留了大小信息。(注:我找不到这方面的权威证据;这可能是在标准中没有说明它应该递归发生的情况下隐含的。)
本文最初发表于 The other branch

脚注

  • 1 这是 C++98 和 03 中指定的列表(参见 ISO C++ 标准 14882 14.1.4);C++11 增加了一些内容。
  • 2 Stroustrup - The C++ Programming Language, Special Edition;附录 C.13.4 - Deducing Function Template Arguments
  • 3 ISO C++ 标准 14882 4.2.1。