理解C++中的逗号操作符

最近在阅读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表达式了。

你可能感兴趣的:(c++)