• 文章
  • 從陣列中推導模板引數
作者:
釋出於 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。