最近在阅读drogon的源代码的时候遇到了一段很奇怪的代码,在drogon/utils/Utilities.h文件中,有这么一句
static auto test(U *p, std::stringstream &&ss)
-> decltype((ss >> *p), yes());
这是在一个结构体中声明一个函数。按照C++11的语法规定,这是
auto 函数名(参数列表) -> 返回值类型;
所以这段代码中,函数名为test
参数有2个,第1个是指向U类型对象的指针*p,第2个是字符串流std::stringstream的右值引用ss
而返回值是decltype((ss >> *p), yes())
在C++11中decltype关键字的使用方法如下
https://zh.cppreference.com/w/cpp/language/decltype
decltype(object)
// or
decltype(expression)
也就是说decltype后面的括号里应该只有1个表达式。
不过在decltype((ss >> *p), yes())中确是用逗号","分隔的两个表达式。实际上这是一个C++表达式
https://zh.cppreference.com/w/cpp/language/operator_other#Built-in_comma_operator
这个逗号操作符的意思是说,先执行逗号左侧的表达式,其结果不销毁,然后再执行右侧的表达式,然后销毁左侧的结果,并把右侧的结果返回。如果有多个逗号,则按照顺序从左向右一个一个执行,并最后返回最右侧的表达式的结果。
也就是说,(ss >> *p), yes()是1个表达式,这个表达式里的这个","是一个操作符。
如果这段代码执行的话,先执行(ss >> *p),结果先不销毁,再执行yes(),然后把(ss >> *p)的结果销毁,最后返回yes()的结果。
为什么要如此大费周章呢?这是因为作者希望通过(ss >> *p)这个表达式确保变量类型的正确,只有在(ss >> *p)正确执行的前提下,才能执行yes()表达式。
(ss >> *p)这个表达式又是怎么保证变量类型的正确的呢?源文件中ss这个变量是std::stringstream,其中包含了>>流操作符,std::stringstream继承自std::basic_stream,而std::basic_stream又是继承自std::istream和std::ostream,而>>流操作符来自std::istream。
我翻看了gcc自带的stl中std::istream的源代码,发现>>流操作符其实是对std::forward的封装,而std::forward中有static_cast,换句话说,如果*p不能被static_cast为std::stringstream能够接受的类型——如std::basic_string或char*,那么代码在编译期检查时将会无法通过。
而decltype(expression)本来就不是要执行expression,而是通过语法分析得到expression返回值的类型,所以这完全符合开发者的本意。
最后总结一下。这段代码是这样的:通过逗号操作符左侧的表达式来确保类型正确,然后通过右侧的表达式的返回值来推断类型,最后再使用c++11的函数定义语法来定义。
对于逗号的使用,还有另外一个场景,就是函数形参或者实参列表。在这里逗号是分隔符,不是操作符,要特别注意两者的区别。
syntax - C++ What does 'int x = (anyInt1, anyInt2);' mean? - Stack Overflow
对两者的区别做了强调。
其实逗号运算符甚至还能重载。在
https://www.geeksforgeeks.org/overloading-the-comma-operator/
就提供了重载的例子。
在
https://stackoverflow.com/questions/5602112/when-to-overload-the-comma-operator
则明确支出在Boost.Assign库中有逗号运算符的重载。而Boost.Phoenix中本来也有逗号运算符的重载,不过新版似乎换成lambda表达式了。