Open zhangyachen opened 3 years ago
模板参数接收任意数量的参数。
定义:
void print() {} template <typename T, typename... Types> void print(T firstArg, Types... args) { std::cout << firstArg << '\n'; // print first argument print(args...); // call print() for remaining arguments }
使用:
std::string s("world"); print (7.5, "hello", s);
C和GO都有类似的概念和定义方式,很好理解。定义void print() {}是为了终止递归。
void print() {}
args被叫做function parameter pack.
args
function parameter pack
返回parameter pack个数:
parameter pack
template<typename T, typename... Types> void print (T firstArg, Types... args) { std::cout << sizeof...(Types) << '\n'; // print number of remaining types ... }
也许有人会想利用sizeof...来判断:只有当可变参数模板的参数个数大于0时,才调用print,这样可以省略void print() {}:
sizeof...
print
template <typename T, typename... Types> void print(T firstArg, Types... args) { std::cout << firstArg << '\n'; if (sizeof...(args) > 0) { // error if sizeof...(args)==0 print(args...); // and no print() for no arguments declared } }
但是这样是错误的,因为模板在编译阶段也会将if的所有代码都进行编译,而不会去根据if的条件去进行选择性的编译,选择运行if的哪个分支是在运行期间做的。
但是c++17引入了编译期的if(Compile-Time If),所以上面的代码可以这么写:
template <typename T, typename... Types> void print(T const &firstArg, Types const &... args) { std::cout << firstArg << '\n'; if constexpr (sizeof...(args) > 0) { print(args...); // code only available if sizeof...(args)>0 (since C++17) } }
if constexpr是c++17中编译期if的语法。这样就可以进行在编译期决定编译if条件的哪个分支。再举个例子:
if constexpr
template <typename T> std::string asString(T x) { if constexpr(std::is_same_v<T, std::string>) { return x; //如果T不是string就是无效的语句 } else if constexpr(std::is_arithmetic_v<T>) { return std::to_string(x); //如果x不是数字就是无效的语句 } else { return std::string(x); //如果不能转换为string就是无效的语句。 } }
从c++17开始,折叠表达式可以将二元运算符作用于所有parameter pack的参数上:
比如求parameter pack的和:
template<typename... T> auto foldSum (T... s) { return (... + s); // ((s1 + s2) + s3) ... }
再比如上面的print例子可以简写成:
template<typename... Types> void print (Types const&... args) { (std::cout << ... << args) << '\n'; }
如果想要在每个参数中间输出空格,可以配合lambda:
template <typename FirstType, typename... Args> void print(FirstType first, Args... args) { std::cout << first; auto printWhiteSpace = [](const auto arg) { std::cout << " " << arg; }; (..., printWhiteSpace(args)); } int main() { print("hello","world","zhangyachen"); }
其中, (..., printWhiteSpace(args));会被展开为:printWhiteSpace(arg1), printWhiteSpace(arg2), printWhiteSpace(arg3)这样的格式。
(..., printWhiteSpace(args));
printWhiteSpace(arg1), printWhiteSpace(arg2), printWhiteSpace(arg3)
比如将每个parameter pack的参数double:
template<typename... T> void printDoubled (T const&... args) { print (args + args...); } printDoubled(7.5, std::string("hello"), std::complex<float>(4,2));
上面的调用会展开为:
print(7.5 + 7.5, std::string("hello") + std::string("hello"), std::complex<float>(4,2) + std::complex<float>(4,2);
如果只是想加1,可以改为:
template<typename... T> void addOne (T const&... args) { print (args + 1...); // ERROR: 1... is a literal with too many decimal points print (args + 1 ...); // OK print ((args + 1)...); // OK }
还可以用在Compile-time Expression中,比如下面的函数会判断所有的参数类型是否一致:
Compile-time Expression
template<typename T1, typename... TN> constexpr bool isHomogeneous (T1, TN...) { return (std::is_same<T1,TN>::value && ...); // since C++17 } isHomogeneous(43, -1, "hello");
std::is_same<int,int>::value && std::is_same<int,char const*>::value // false
template<typename C, typename... Idx> void printElems (C const& coll, Idx... idx) { print (coll[idx]...); } std::vector<std::string> coll = {"good", "times", "say", "bye"}; printElems(coll,2,0,3);
最后的调用相当于:
print (coll[2], coll[0], coll[3]);
比如标准库的Tuple:
template<typename... Elements> class Tuple; Tuple<int, std::string, char> t; // t can hold integer, string, and character
namespace std { template <typename T, typename... U> array(T, U...) -> array<enable_if_t<(is_same_v<T, U> && ...), T>, (1 + sizeof...(U))>; } std::array a{42,45,77};
关键点:
enable_if_t
is_same_v<T, U> && ...
c++17的新特性,中文翻译应该叫:变长的using声明。C++17尝鲜:变长 using 声明这篇文章关于using的来龙去脉讲的很清楚,推荐大家看看。
一个更实际的例子:
class Customer { private: std::string name; public: Customer(std::string const &n) : name(n) {} std::string getName() const { return name; } }; struct CustomerEq { bool operator()(Customer const &c1, Customer const &c2) const { return c1.getName() == c2.getName(); } }; struct CustomerHash { std::size_t operator()(Customer const &c) const { return std::hash<std::string>()(c.getName()); } }; // define class that combines operator() for variadic base classes: template <typename... Bases> struct Overloader : Bases... { using Bases::operator()...; // OK since C++17 }; int main() { // combine hasher and equality for customers in one type: using CustomerOP = Overloader<CustomerHash, CustomerEq>; std::unordered_set<Customer, CustomerHash, CustomerEq> coll1; std::unordered_set<Customer, CustomerOP, CustomerOP> coll2; ... }
这里给unordered_set提供自定义的Hash和KeyEqual。
Hash
KeyEqual
关于可变参数模板的应用场景和各种使用技巧有很多,这里只列了5种大方向的应用场景,但是起码下次遇到看不懂的地方时,知道往哪个大方向去查,不至于一头雾水 :)
(完)
欢迎大家关注我的知乎账号:https://www.zhihu.com/people/zhangyachen
朋友们可以关注下我的公众号,获得最及时的更新:
模板参数接收任意数量的参数。
定义与使用
定义:
使用:
C和GO都有类似的概念和定义方式,很好理解。定义
void print() {}
是为了终止递归。args
被叫做function parameter pack
.sizeof...
返回
parameter pack
个数:也许有人会想利用
sizeof...
来判断:只有当可变参数模板的参数个数大于0时,才调用print
,这样可以省略void print() {}
:但是这样是错误的,因为模板在编译阶段也会将if的所有代码都进行编译,而不会去根据if的条件去进行选择性的编译,选择运行if的哪个分支是在运行期间做的。
Compile-Time If
但是c++17引入了编译期的if(Compile-Time If),所以上面的代码可以这么写:
if constexpr
是c++17中编译期if的语法。这样就可以进行在编译期决定编译if条件的哪个分支。再举个例子:折叠表达式 Fold Expressions
从c++17开始,折叠表达式可以将二元运算符作用于所有
parameter pack
的参数上:比如求
parameter pack
的和:再比如上面的
print
例子可以简写成:如果想要在每个参数中间输出空格,可以配合lambda:
其中,
(..., printWhiteSpace(args));
会被展开为:printWhiteSpace(arg1), printWhiteSpace(arg2), printWhiteSpace(arg3)
这样的格式。其他场景
Variadic Expressions
比如将每个
parameter pack
的参数double:上面的调用会展开为:
如果只是想加1,可以改为:
还可以用在
Compile-time Expression
中,比如下面的函数会判断所有的参数类型是否一致:上面的调用会展开为:
Variadic Indices
最后的调用相当于:
Variadic Class Templates
比如标准库的Tuple:
Variadic Deduction Guides
关键点:
enable_if_t
控制是否启用该模板。 这个后面文章会讲到。is_same_v<T, U> && ...
判断数组元素类型是否相同,跟上面提到的例子用法一样。Variadic Base Classes and using
c++17的新特性,中文翻译应该叫:变长的using声明。C++17尝鲜:变长 using 声明这篇文章关于using的来龙去脉讲的很清楚,推荐大家看看。
一个更实际的例子:
这里给unordered_set提供自定义的
Hash
和KeyEqual
。关于可变参数模板的应用场景和各种使用技巧有很多,这里只列了5种大方向的应用场景,但是起码下次遇到看不懂的地方时,知道往哪个大方向去查,不至于一头雾水 :)
(完)
欢迎大家关注我的知乎账号:https://www.zhihu.com/people/zhangyachen
朋友们可以关注下我的公众号,获得最及时的更新: