C++ Variadic Template 可變樣板參數的樣板 Sample Code

完整 Code ,底下有一段一段解釋。
#include <iostream>
#include <typeinfo>
using namespace std;

// helper function to print amount of remaining arguments.
template<typename... T> void printSize(T... args);  

// declaration for template  specialization, no need to implement.
template <typename... T> void checkType(T... args); 

// end of recursive
template <> void checkType(); 

// use function overloading to capture the first object/builtin type in argument list(T...)
template <typename... T> void checkType(const short& v, T... args);
template <typename... T> void checkType(const char& v, T... args);
template <typename... T> void checkType(const int& v, T... args);
template <typename... T> void checkType(const long& v, T... args);
template <typename... T> void checkType(const long long& v, T... args);
template <typename... T> void checkType(const float& v, T... args);
template <typename... T> void checkType(const double& v, T... args);
template <typename... T> void checkType(const char* &v, T... args); // missing wchar_t* 
template <typename... T> void checkType(const string &v, T... args);
template <typename... T> void checkType(const wstring &v, T... args);

// default case for capture
template <typename UnknownType, typename... T> void checkType(const UnknownType& un, T... args);

template<typename... T>
void printSize(T... args)
{
    cout << "  [" << sizeof...(args) << " arguments left] ";
}

template <>
void checkType()
{
    cout << "end of arguments type checking..." << endl;
}

template <typename UnknownType, typename... T>
void checkType(const UnknownType& un, T... args)
{
    cout << "  [" << sizeof...(args) << "]";
    cout << "unknown type detected. typeid name: " << typeid(un).name() << endl;
    checkType(args...);
}

template <typename... T> void checkType(const short& v, T... args)
{
    printSize(args...);
    cout << "short detected" << endl;
    checkType(args...);
}
template <typename... T> void checkType(const char* & v, T... args)
{
    printSize(args...);
    cout << "c string detected" << endl;
    checkType(args...);
}
template <typename... T> void checkType(const int& v, T... args)
{
    printSize(args...);
    cout << "int detected" << endl;
    checkType(args...);
}
template <typename... T> void checkType(const long& v, T... args)
{
    printSize(args...);
    cout << "long detected" << endl;
    checkType(args...);
}
template <typename... T> void checkType(const long long& v, T... args)
{
    printSize(args...);
    cout << "longlong detected" << endl;
    checkType(args...);
}
template <typename... T> void checkType(const float& v, T... args)
{
    printSize(args...);
    cout << "float detected" << endl;
    checkType(args...);
}

template <typename... T> void checkType(const double& v, T... args)
{
    printSize(args...);
    cout << "double detected" << endl;
    checkType(args...);
}


template <typename... T> void checkType(const char& v, T... args)
{
    printSize(args...);
    cout << "char detected" << endl;
    checkType(args...);
}

template <typename... T> void checkType(const string& v, T... args)
{
    printSize(args...);
    cout << "string detected" << endl;
    checkType(args...);
}
template <typename... T> void checkType(const wstring& v, T... args)
{
    printSize(args...);
    cout << "wstring detected" << endl;
    checkType(args...);
}

int main()
{   
   checkType(1.1f, 2.2, short(1), 2, 3l, 4ll, "Hello c string", L"Hello wide c string", string("hello string"), wstring(L"hello wstring"));
   return 0;
}
output
  [9 arguments left] float detected
  [8 arguments left] double detected
  [7 arguments left] short detected
  [6 arguments left] int detected
  [5 arguments left] long detected
  [4 arguments left] longlong detected
  [3 arguments left] c string detected
  [2]unknown type detected. typeid name: PKw
  [1 arguments left] string detected
  [0 arguments left] wstring detected
end of arguments type checking...

開頭

#include <iostream>
#include <typeinfo>
using namespace std;
  • 使用 cout 印出 XXX type detected... 。
  • 在遇到 Unknown type 時,使用 typeinfo RTTI 來印 name()
  • 方便實作用,所以 using namespace std; 一下

Function Template 宣告

// helper function to print amount of remaining arguments.
template<typename... T> void printSize(T... args);  

// declaration for template  specialization, no need to implement.
template <typename... T> void checkType(T... args); 

// end of recursive
template <> void checkType(); 

// use function overloading to capture the first object/builtin type in argument list(T...)
template <typename... T> void checkType(const short& v, T... args);
template <typename... T> void checkType(const char& v, T... args);
template <typename... T> void checkType(const int& v, T... args);
template <typename... T> void checkType(const long& v, T... args);
template <typename... T> void checkType(const long long& v, T... args);
template <typename... T> void checkType(const float& v, T... args);
template <typename... T> void checkType(const double& v, T... args);
template <typename... T> void checkType(const char* &v, T... args); // missing wchar_t* 
template <typename... T> void checkType(const string &v, T... args);
template <typename... T> void checkType(const wstring &v, T... args);

// default case for capture
template <typename UnknownType, typename... T> void checkType(const UnknownType& un, T... args);

為什麼要用宣告式?

  • 本來沒有用 Function Template 宣告,而是直接硬寫一個一個,會發現會因為各 function overloading 會需要知道,每一個 function overloading 的 function signature ,才能會正常選擇,否則會一直呼叫到非預期的 function template。 像是 checkType(int &v, T... args); 寫在 checkType(double& v T... args); 的前面時,在 check int 的 Function Template 裡,會無法認得 check double 的 Function Template。最後只好先定出宣告式,後面再去定義完整的 template 實作。

程式流程

當我們呼叫 checkType(1); 時,Function overloading 的 candidating 會開始運作。template <typename... T> void checkType(T... args); 的 Function Template 會被選擇到。進而開始選擇它的全特化版本(Function Template 無法偏特化。)。
入選會去看 function name 與 function arguments amount。

`checkType(T... args);` 與它的全特化版本兄弟們。
checkType(T... args); // 0...any 數量 OK
checkType(const short& v, T... args); // 0...any 數量 OK
checkType(const char& v, T... args); // 0...any 數量 OK
checkType(const int& v, T... args); // 0...any 數量 OK
checkType(const long& v, T... args); // 0...any 數量 OK
checkType(const long long& v, T... args); // 0...any 數量 OK
checkType(const float& v, T... args); // 0...any 數量 OK
checkType(const double& v, T... args); // 0...any 數量 OK
checkType(const char* &v, T... args); // 0...any 數量 OK
checkType(const string &v, T... args); // 0...any 數量 OK
checkType(const wstring &v, T... args); // 0...any 數量 OK
checkType(const UnknownType& un, T... args); // 0...any 數量 OK
checkType();  // only 0 數量 OK

名字對了 + 參數數量對了

checkType(T... args); // no need conversion
checkType(const short& v, T... args); // need std conversion
checkType(const char& v, T... args); // need std conversion
checkType(const int& v, T... args); // no need conversion
checkType(const long& v, T... args); // need promotion
checkType(const long long& v, T... args); // need promotion
checkType(const float& v, T... args); // need std conversion
checkType(const double& v, T... args); // need std conversion
checkType(const char* &v, T... args); // can't convert
checkType(const string &v, T... args); // can't convert
checkType(const wstring &v, T... args); // can't convert
checkType(const UnknownType& un, T... args); // no need conversion

名字對了 + 參數數量對了 + type 可以轉

checkType(T... args); 
checkType(const short& v, T... args);
checkType(const char& v, T... args);
checkType(const int& v, T... args);
checkType(const long& v, T... args);
checkType(const long long& v, T... args);
checkType(const float& v, T... args);
checkType(const double& v, T... args);
checkType(const UnknownType& un, T... args);

名字對了 + 參數數量對了 + type 轉換次數最少

checkType(T... args); 
checkType(const int& v, T... args);
checkType(const UnknownType& un, T... args);

名字對了 + 參數數量對了 + type 轉換次數最少 + compiler 喜歡特化版

checkType(const int& v, T... args);
checkType(const UnknownType& un, T... args);

名字對了 + 參數數量對了 + type 轉換次數最少 + compiler 喜歡特化版 + compiler 喜歡明確 type

checkType(const int& v, T... args);

在呼叫 checkType(1); 會選擇 checkType(const int& v, T... args);

template <typename... T> void checkType(const int& v, T... args)
{
    printSize(args...);
    cout << "int detected" << endl;
    checkType(args...);
}
  • checkType(1); 也就是 checkType(1, void); 傳入 checkType(const int& v, T... args);,所以 void 空參數進入了 args。
  • 接著 含著 void 的 args 會被傳入 printSize(T... args); 來去印 arguments 的數量,用法是使用 operator sizeof...,對著 args 運算。
    template<typename... T>
    void printSize(T... args)
    {
    cout << "  [" << sizeof...(args) << " arguments left] ";
    }
    
  • 將含有 void 的 args 遞迴傳遞下去。會讓 void checkType(); 這個特化 template 接到,來做到整個遞迴的結束。
    template <>
    void checkType()
    {
    cout << "end of arguments type checking..." << endl;
    }
    

若 client call 了 checkType(1, 2.2, "Hi", MyType());

  • checkType(1, 2.2, "Hi", MyType()); -> checkType(const int&, T...)
  • checkType(2.2, "Hi", MyType()); -> checkType(const double&, T...)
  • checkType("Hi", MyType()); -> checkType(const char* &, T...)
  • checkType(MyType()); -> checkType(const UnknownType&, T...)
  • checkType(); -> checkType()

為什麼 template <typename... T> void checkType(T... args); 不用實作?

因為它不會被 call 到,所有的遞迴都只會由 1.第一參數是明確的。 2.第一參數是 template。3.沒有參數。的 Function Template 去 call ,來減少 argument list 的數量,才不會無窮迴圈(arguments 永遠不減少。)。void checkType(const UnknownType& un, T... args); 會用來接不知名型別的狀況。

那可以刪掉 template <typename... T> void checkType(T... args); 不宣告嗎?

不行。因為在參數數量上必須相容。

  • checkType()[0 個參數] 與 checkType(const UnknownType&, T... args)[1個以上的參數],都可以算是與 checkType(T... args) [0個以上的參數] 相容,是"他的一種",是他的特化版。

  • 如果你拿 checkType(const UnknownType&, T... args) 當 Function Template 的 原始樣板,那麼 checkType() 就不能說算是 一種 1 個參數以上的 checkType,也就是無法成為他的特化版。

comments powered by Disqus