93

I see questions on SO every so often about overloading the comma operator in C++ (mainly unrelated to the overloading itself, but things like the notion of sequence points), and it makes me wonder:

When should you overload the comma? What are some examples of its practical uses?

I just can't think of any examples off the top of my head where I've seen or needed to something like

foo, bar;

in real-world code, so I'm curious as to when (if ever) this is actually used.

Forever Learner
  • 1,465
  • 4
  • 15
  • 30
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    Now that C++ has uniform initialization syntax, most of these techniques are unnecessary. – Dan Jul 11 '14 at 12:41

11 Answers11

177

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;
Petter
  • 37,121
  • 7
  • 47
  • 62
  • 41
    I actually really like this, +1. – ildjarn Nov 19 '14 at 20:20
  • 15
    On the other hand, this is to overcome the fact that we can only pass one parameter to `operator[]`. Some have proposed that it can take several parameters: see [Evolution Defect Report 88](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4325.html#88). – Morwenn Feb 05 '15 at 22:47
  • 2
    It feels like a great syntax to also use for multidimensional array implementation but unfortunately not so well overloaded for integral types. – sim642 Nov 11 '15 at 20:59
  • Ignoring the fact territories change (less than 100 years ago Prussia was still a valid European state), using the comma operator to create tuples is perfectly valid and useful. – Pharap May 16 '17 at 15:06
  • 26
    `distance[{new_york, washington}]` works without overloading anything. An extra set of brackets is a small price to pay to avoid something so evil! – Arthur Tacca Jun 14 '18 at 18:00
  • 1
    It is too bad that we can not change the language so that a,b could mean something like std::tie(a, b). – Petter Jun 15 '18 at 19:05
  • 9
    What happens if you call a function ```foo(new_york, washington)```, that should take two seperate places as arguments? – OutOfBound Nov 18 '19 at 17:01
  • @OutOfBound not difficult to test and see https://godbolt.org/z/zG6xoafos – Aykhan Hagverdili Jun 03 '22 at 13:24
  • 5
    This is unnecessary as of C++23 (), which supports multi-argument `operator[]`. – user3840170 Oct 08 '22 at 15:17
81

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:

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • 8
    But +1 for the exception. :P Would you mind elaborating a bit on the template metaprogramming use of `operator,`? It sounds really interesting. – user541686 Apr 09 '11 at 01:09
  • @Mehrdad : It'd be too long to put in a comment, and I don't recall offhand any articles about it online except maybe a few by Eric Niebler (check his website, they're all worth a read). In my personal experience, I've never run across an instance where I couldn't use overload resolution + `...` to solve my problem instead (which I personally find easier to understand than operator precedence + `operator,`). – ildjarn Apr 09 '11 at 01:19
  • 2
    Also, Boost.Parameter overloads the comma operator, which is another use. Also, I agree that the comma operator should almost never be overloaded. Its difficult to use effectively, because of its low precedence. – Paul Fultz II Feb 23 '12 at 05:07
  • 2
    you can also find it in Eigen. – Gabrielle de Grimouard Jul 24 '17 at 14:33
  • I downvoted your answer since it is self-contradictory. First you state that you should never overload the `,` operator, then you write that useful overloads of it do exist. – HelloGoodbye Nov 28 '18 at 11:34
  • 1
    @HelloGoodbye : Except that I never stated that one should never overload it; I stated that you, as the asker of this question, should never overload it. I.e. if you don't already know the answer, then the answer is never – this is not self-contradictory in any way IMO. Thanks for explaining the downvote though. :-] – ildjarn Nov 29 '18 at 04:33
  • Fair enough! ;) – HelloGoodbye Nov 29 '18 at 13:10
  • 1
    The low precedence allows composition of almost all imaginable expressions without requiring additional parentheses - that's a very neat property of that operator. It does come handy and over the years I've found plenty of uses for it that made the code readable and expressive... but my rule is to use it only when it introduces no surprises and makes the meaning of the code obvious even to someone who didn't read the documentation of the API in use. – Kuba hasn't forgotten Monica Mar 28 '19 at 12:23
  • I would deprecate Boost.Assign in favour of std::initializer_list, and Boost.Phoenix in favour of native lambdas. They were better than nothing before C++11 tho. – Caleth Jun 09 '21 at 13:31
  • I'd add, that in modern code Variadic Templates make the use of the overloaded comma operator obsolete in most cases. – Sparkofska Nov 18 '21 at 10:13
  • Turned around, your answer means that "you" should never develop a Boost library. Have more passion please! – Johan Boulé Jan 06 '22 at 13:22
  • @Johan : Or, have some respect for the practicality of generalizations, please! – ildjarn Jan 06 '22 at 13:25
40

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.)

Community
  • 1
  • 1
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    Meh, can't find it. *Very* corner-case stuff. – GManNickG Apr 09 '11 at 00:55
  • +1 Whoa I hadn't seen that before, cool! (Though I doubt I'll be using it often, haha.) – user541686 Apr 09 '11 at 01:09
  • 1
    But seriously, would you really want to write code like this? To someone reading your code, this will be utterly confusing. I assume that snippet is a shorthand for a `push_back` on those 8 values, but it _looks like_ 9 is being added to a `vector`, which doesn't make any sense. Frankly, this is a strong counter argument for Boost being a "high quality library". Code should be clear and obvious. Otherwise, one could as well implement something like `T& operator--(int){ delete this; return *this; }`, which would probably work fine, too. It's just not obvious to someone else _what_ happens. – Damon Apr 09 '11 at 12:04
  • @Damon: "it looks like 9 is being added" - it not only looks like that, 9 itself also gets added. Why wouldn't it? – Xeo Apr 10 '11 at 08:12
  • 2
    Well, operator+= adds, in common understanding, the value of the expression on the right side. The expression 1,2,...9 evaluates to 9 in common understanding. Overloading operators subverts the semantics, and while it is syntactically valid, that does not mean it is necessarily good. Operator overloading is good if it makes code clear, but here it makes code ambiguous and confusing (at least in my feeling). It is much different with e.g. initializer_list assignment in C++0x because the curly braces make it immediately obvious what's going on. Also, I deem overloading operator+= for a vector... – Damon Apr 10 '11 at 09:31
  • 6
    ... as maybe not one of the wisest choices, because there are at least two equally valid interpreations of that operator on a vector. I assume that "append element(s) to end" is what's meant here, but it could equally well be "invoke operator += on each element in vector with these arguments". It might very well be defined only for sets of equal size, or it might zero-extend the smaller set, or whatever... thing is, you don't know without intensely studying documentation, it is not obvious. Good code is obvious without explanation. – Damon Apr 10 '11 at 09:36
  • 3
    As another example, I remember running across a string class a few years ago which overloaded `operator<=`. That allowed you to write cool code like `str <= "foo";`. Except it isn't cool at all when the next person reading your code says "what the hell?" and it becomes totally uncool the first time you spend a week debugging for nothing because someone didn't know and wrote something like `if(str <= "bar")`. – Damon Apr 10 '11 at 09:50
  • [`dlib::matrix` also does the same](https://stackoverflow.com/q/67108085/995714) – phuclv Apr 15 '21 at 14:59
23

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.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • 9
    "I let the reader figure out as an exercise what is going on." not the point of an answer site. – infinitezero Feb 11 '21 at 10:33
  • @infinitezero It's not hard, and you should never use that in production, especially not since five new standards have been out since that answer. – Alexandre C. Feb 14 '21 at 23:52
  • The code does not work: you missed a pair of `()` on the last line. Also, `operator,` associates from left to right, so I am not sure what your last sentence means. Actually, `::detail_::tag(),` can be completely omitted from the macro definition. – Yongwei Wu Jun 02 '22 at 12:26
16

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;
Community
  • 1
  • 1
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
14

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".

CygnusX1
  • 20,968
  • 5
  • 65
  • 109
10

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.

Community
  • 1
  • 1
moooeeeep
  • 31,622
  • 22
  • 98
  • 187
6

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...)

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
5

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?

AC.
  • 1,005
  • 2
  • 12
  • 20
  • 1
    I don't know if it is bad or not. But avoids writing code like this: `... << "This is a message on line " << std::to_string(__LINE__) << " because variable a = " << std::to_string(a) << " which is larger than " << std::to_string(limit) << "\n"`. Which is very common in reporting errors or building messages for exceptions. I am not sure if comma was the only choice: any other operator could have achieved this, for example `operator+` or `operator|` or `operator&&` or even `operator<<` itself. But it is an interting case. – alfC Dec 02 '13 at 20:30
  • 5
    I think modern C++ would use variadic tempates instead. – Petter Aug 09 '14 at 12:13
  • 1
    It's bad to answer questions with questions ;-) – lmat - Reinstate Monica Feb 23 '16 at 16:30
5

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+=`
  }
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Maybe by now it should be `template X& ADD(X& buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}`. Note: you probably have to prevent optimization of sink with a `(void) sink;` statement. This dodges the macro, which is, imo, even better – WorldSEnder Mar 08 '15 at 12:06
3

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);
Aleksei Petrenko
  • 6,698
  • 10
  • 53
  • 87