何时重载逗号运算符?

我经常看到关于 SO 的问题,是关于在 C + + 中重载逗号运算符的(主要与重载本身无关,但是像序列点这样的概念) ,这让我想知道:

应该超载逗号时,有哪些实际应用的例子?

我只是想不出任何例子,在我的头顶上,我已经看到或需要类似的东西

foo, bar;

在现实世界的代码,所以我很好奇什么时候(如果曾经)这实际使用。

32300 次浏览

Boost.Assign uses it, to let you do things like:

vector<int> v;
v += 1,2,3,4,5,6,7,8,9;

And I've seen it used for quirky language hacks, I'll see if I can find some.


Aha, I do remember one of those quirky uses: collecting multiple expressions. (Warning, dark magic.)

Let's change the emphasis a bit to:

When should you overload the comma?

The answer: Never.

The exception: If you're doing template metaprogramming, operator, has a special place at the very bottom of the operator precedence list, which can come in handy for constructing SFINAE-guards, etc.

The only two practical uses I've seen of overloading operator, are both in Boost:

One possibility is the Boost Assign library (though I'm pretty sure some people would consider this abuse rather than a good use).

Boost Spirit probably overloads the comma operator as well (it overloads almost everything else...)

Similar to @GMan's Boost.Assign example, Blitz++ overloads the comma operator to provide a convenient syntax for working with multidimensional arrays. For example:

Array<double,2> y(4,4);   // A 4x4 array of double
y = 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;

The comma has an interesting property in that it can take a parameter of type void. If it is the case, then the built-in comma operator is used.

This is handy when you want to determine if an expression has type void:

namespace detail_
{
template <typename T>
struct tag
{
static T get();
};


template <typename T, typename U>
tag<char(&)[2]> operator,(T, tag<U>);


template <typename T, typename U>
tag<U> operator,(tag<T>, tag<U>);
}


#define HAS_VOID_TYPE(expr) \
(sizeof((::detail_::tag<int>(), \
(expr), \
::detail_::tag<char>).get()) == 1)

I let the reader figure out as an exercise what is going on. Remember that operator, associates to the right.

Along the same lines, I was sent a github pull request with comma operator overload. It looked something like following

class Mylogger {
public:
template <typename T>
Mylogger & operator,(const T & val) {
std::cout << val;
return * this;
}
};


#define  Log(level,args...)  \
do { Mylogger logv; logv,level, ":", ##args; } while (0)

then in my code I can do:

 Log(2, "INFO: setting variable \", 1, "\"\n");

Can someone explain why this is a good or bad usage case?

In SOCI - The C++ Database Access Library it is used for the implementation of the inbound part of the interface:

sql << "select name, salary from persons where id = " << id,
into(name), into(salary);

From the rationale FAQ:

Q: Overloaded comma operator is just obfuscation, I don't like it.

Well, consider the following:

"Send the query X to the server Y and put result into variable Z."

Above, the "and" plays a role of the comma. Even if overloading the comma operator is not a very popular practice in C++, some libraries do this, achieving terse and easy to learn syntax. We are pretty sure that in SOCI the comma operator was overloaded with a good effect.

I have used the comma operator in order to index maps with multiple indices.

enum Place {new_york, washington, ...};


pair<Place, Place> operator , (Place p1, Place p2)
{
return make_pair(p1, p2);
}




map< pair<Place, Place>, double> distance;


distance[new_york, washington] = 100;

Here is an example from OpenCV documentation (http://docs.opencv.org/modules/core/doc/basic_structures.html#mat). The comma operator is used for cv::Mat initialization:

// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);

One of the practical usage is for effectively using it with variable arguments in macro. By the way, variable arguments was earlier an extension in GCC and now a part of C++11 standard.

Suppose we have a class X, which adds object of type A into it. i.e.

class X {
public: X& operator+= (const A&);
};

What if we want to add 1 or more objects of A into X buffer;?
For example,

#define ADD(buffer, ...) buffer += __VA_ARGS__

Above macro, if used as:

ADD(buffer, objA1, objA2, objA3);

then it will expand to:

buffer += objA1, objeA2, objA3;

Hence, this will be a perfect example of using comma operator, as the variable arguments expand with the same.

So to resolve this we overload comma operator and wrap it around += as below

  X& X::operator, (const A& a) {  // declared inside `class X`
*this += a;  // calls `operator+=`
}

I use the comma operator for printing log output. It actually is very similar to ostream::operator<< but I find the comma operator actually better for the task.

So I have:

template <typename T>
MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }

It has these nice properties

  • The comma operator has the lowest priority. So if you want to stream an expression, things do not mess up if you forget the parenthesis. Compare:

    myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
    myLog, "The result is: ", x&y;
    

    you can even mix comparisons operators inside without a problem, e.g.

    myLog, "a==b: ", a==b;
    
  • The comma operator is visually small. It does not mess up with reading when gluing many things together

    myLog, "Coords=", g, ':', s, ':', p;
    
  • It aligns with the meaning of the comma operator, i.e. "print this" and then "print that".