我正在努力理解 C 中的模板引數包擴展。出于演示目的,我想撰寫一個函式來列印簽名中使用的型別的人類可讀名稱(任何簽名 - 這就是可變引數模板的用武之地)。
#include <boost/type_index.hpp>
std::string SignatureToString()
{
return std::string();
}
template<typename Arg1, typename... Args>
std::string SignatureToString(const Arg1&, Args&&... args)
{
std::string strRetVal = boost::typeindex::type_id<Arg1>().pretty_name();
std::string strRemainingSignature = SignatureToString(args...); // expanding parameters works
if (!strRemainingSignature.empty())
{
strRetVal = strRetVal ", " strRemainingSignature;
}
return strRetVal;
}
class cDog {/* ... */};
int main(int /*argc*/, char* /*argv*/[])
{
int i(0);
std::string str;
//cDog someDog("Harry"); // don't want to construct this dummy object!
std::cout << "GetSignature(): '" << SignatureToString() << "'" << std::endl;
std::cout << "GetSignature(int): '" << SignatureToString(i) << "'" << std::endl;
std::cout << "GetSignature(std::string): '" << SignatureToString(str) << "'" << std::endl;
//std::cout << "GetSignature(cDog): '" << SignatureToString(someDog) << "'" << std::endl;
std::cout << "GetSignature(int, std::string): '" << SignatureToString(i, str) << "'" << std::endl;
//std::cout << "GetSignature(int, std::string, cDog): '" << SignatureToString(i, str, someDog) << "'" << std::endl;
}
上面的例子有效。由于 boost::typeindex::type_id 并不真正需要傳遞的型別為 Arg1 的變數(并且某些型別 - 例如 cDog - 可能不是或不容易是虛擬構造的),我想我可以將實作更改為下面的(相同的模板引數,但沒有傳遞這些型別的引數包)并通過擴展型別包而不是引數包來遞回呼叫模板。我閱讀https://en.cppreference.com/w/cpp/language/parameter_pack部分“模板引數串列”意味著這應該是可能的,但我的編譯器 (vc141) 給我錯誤 C2672(未找到匹配的簽名)。
編輯:錯誤描述顯然與模板決議有關,但我看不到對我的問題的適用性。https://learn.microsoft.com/en-gb/cpp/error-messages/compiler-errors-2/compiler-error-c2672?view=msvc-150
template<typename Arg1, typename... Args>
std::string SignatureToString()
{
std::string strRetVal = boost::typeindex::type_id<Arg1>().pretty_name(); // this line apparently still works (makes sense)
std::string strRemainingSignature = SignatureToString<Args...>(); // expanding type pack does not work (...?)
if (!strRemainingSignature.empty())
{
strRetVal = strRetVal ", " strRemainingSignature;
}
return strRetVal;
}
int main(int /*argc*/, char* /*argv*/[])
{
std::cout << "GetSignature(): '" << SignatureToString() << "'" << std::endl;
std::cout << "GetSignature(int): '" << SignatureToString<int>() << "'" << std::endl;
std::cout << "GetSignature(std::string): '" << SignatureToString<std::string>() << "'" << std::endl;
return 0;
}
我在這里做錯了什么?
uj5u.com熱心網友回復:
問題在于遞回的停止條件。
要停止您使用的遞回
std::string SignatureToString()
在第一個例子中你稱它為
SignatureToString();
在第二個中你稱它為
SignatureToString<>();
這不起作用,因為它不是模板。
uj5u.com熱心網友回復:
如果你有 C 17,你可以使用if constexpr和折疊運算式。它比遞回函式模板好得多。
template<typename... Args>
std::string SignatureToString()
{
std::string ret;
if constexpr (sizeof...(Args) == 0) {
ret = "";
} else {
ret = ((boost::typeindex::type_id<Args>().pretty_name() ", ") ... );
}
return ret.substr(0, ret.size()-2);
}
uj5u.com熱心網友回復:
正如另一位丹尼爾指出的那樣,問題在于停止遞回。以下不是很漂亮,但它有效。
// Catch call with zero args
std::string SignatureToString()
{
return std::string();
}
// Catch call with exactly one arg
template <typename Arg1>
std::string SignatureToString()
{
return boost::typeindex::type_id<Arg1>().pretty_name();
}
// Catch all other cases
template<typename Arg1, typename Arg2, typename... Args>
std::string SignatureToString()
{
std::string strRetVal = boost::typeindex::type_id<Arg1>().pretty_name();
std::string strRemainingSignature = SignatureToString<Arg2, Args...>();
if (!strRemainingSignature.empty())
{
strRetVal = strRetVal ", " strRemainingSignature;
}
return strRetVal;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/534069.html
標籤:C 模板可变函数
