一、逗号表达式和求值顺序
为什么要分析一下逗号表达式,主要这个应用在c++11以后越来越频繁,而求值顺序又往往在实际应用中有一些奇怪的代码,正是因为求值顺序来控制最终的结果。逗号的运算顺序,自左向右,最后一个为终值,等号的运算顺序,右结合律,即自右向左。明白了这些,就可以在后面的折叠表达式及其相关变参应用中,采用一些技巧达到目的。
这里有一个问题,一定要把求值顺序和运算符的优先级搞清楚,不要混做一谈。如果对优先级有什么不清楚的,好好看看相关书籍资料,而求值顺序可以参考下面的网站:
https://zh.cppreference.com/w/cpp/language/eval_order
二、省略号(…)和折叠表达式
这个省略号,也就是三个点,在c++中到底有什么用处,这里总结一下:
1、变参函数
这个从最初的printf()函数, 到后来的sizeof … ()。使用这些函数有一些要求,比如省略号必须在末尾,必须顺序访问参数。其实可能都明白,类似printf这种函数其实是使用va_arg、va_start、va_end 和 va_list等宏来实现的。如果想实现一个自己的变参函数,可以用这些宏来尝试一下。网上和书上都有很多现成的例子。
2、在c++11以后使用变参模板
这个在前面的模板变参化等文章中有介绍,这里就不再赘述。一般类似下面的样子:
template<typename... T> class Name;
3、变参函数模板
这个在前面也有详述,《c++11中的模板变参化》中有很详实的例子。可以搜索一下前面的BLOG。
template <typename... T>
void TestVarTemplate(T... args)
{
std::cout<< "args len is:"<< sizeof...(args) << std::endl;
}
4、折叠表达式
这个在前面专门分析过。类似于:
template<typename... T>
auto Sum (T... args)
{
return (... + args);
}
5、宏变参
在前面的元编程中就提到过一些宏只也支持变参,类似于:
#define CHECK1(x, ...) if (!(x)) { printf(__VA_ARGS__); }
VA_ARGS 可以替换省略号中的参数,包括它们之间的逗号。
6、异常捕获
比如在try catch(…)用省略号来捕获最后的异常。
这里重点提出了折叠表达式中的省略号的应用,一定要引起重视。
三 、应用
1、变参应用(变参模板、函数变参、宏变参)
这里把前面一个逗号表达式的拿来做例子:
template < typename T>
int Add(T arg)
{
return arg;
}
template < typename... Args>
int AddValue(Args ... args)
{
//也可使用类似int v[] = { (Add(args),0)... };这种逗号表达式代码,只是强制存储0到V数组中
int v[] = { Add(args)... };
int d = 0;
for (auto n : v)
{
d += n;
}
return d;
}
int TestAdd()
{
std::cout << "-----"<< AddValue(3, 3, 3) << std::endl;;
return 0;
}
int main()
{
TestAdd();
system("pause");
return 0;
}
2、折叠表达式中的应用
这里用 (… =)结合前面提到的求值顺序来实现一个向量数组的反向生成:
#include <vector>
template<class ...Args>
auto Rorder(Args&&... args) {
std::vector<std::common_type_t<Args...>>vec {};
bool bOk = false;
(bOk = ... = (vec.push_back(args), false));
return vec;
}
int main()
{
std::vector<int> vec = Rorder(1, 2, 3, 4, 5, 6);
}
四、总结
本文重点是回顾总结,把一些零散的知识点形成一个组合知识点。重点就是通过省略号在变参中的应用来串联一下折叠表达式和求值顺序等形成一个更高一级的总体上的把握,而不是把知识总是纵向切割条理分明。通过横向的把它们编织起来,形成一种知识的网状结构,更容易理解和加深印象。