27

I have made a list class as a means of replacing variadic functions in my program used for initializing objects that need to contain a changing list of elements. The list class has a usage syntax that I really like. However I haven't seen it used before, so I was wondering if I shouldn't use it just because of that fact? A basic implementation of the list class looks like this...

#include <list>
#include <iostream>
template<typename T>
struct list
{
    std::list<T> items;
    list(const list&ref):items(ref.items){}
    list(){}
    list(T var){items.push_back(var);}
    list& operator,(list add_){
        items.insert(items.end(),add_.items.begin(), add_.items.end());
        return *this;
    }
    list& operator=(list add_){
        items.clear();
        items.insert(items.end(),add_.items.begin(), add_.items.end());
        return *this;
    }
    list& operator+=(list add_){
        items.insert(items.end(),add_.items.begin(), add_.items.end());
        return *this;
    }
};

This allows me to have use this in code like so...

struct music{
//...
};
struct music_playlist{
    list<music> queue;
//...
};
int main (int argc, const char * argv[])
{
    music_playlist playlist;
    music song1;
    music song2;
    music song3;
    music song4;
    playlist.queue = song1,song2; // The queue now contains song1 and song2
    playlist.queue+= song1,song3,song4; //The queue now contains two song1s and song2-4
    playlist.queue = song2; //the queue now only contains song2
    return 0;
}

I really think that the syntax is much nicer than it would of been if I had just exposed a regular stl container, and even nicer (and typesafe) than variadic functions. However, since I have not seen this syntax used, I am curious about whether I should avoid it, because above all the code should be easily understood by other programmers?

EDIT:

In joint with this question, I have posted this question more targeted at solutions to the actual problem.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Skyler Saleh
  • 3,961
  • 1
  • 22
  • 35
  • 2
    , gets used a lot in golf, which suggests to me that its usually a bad practice. In this case, it looks more readable for the most part. Maybe avoid using , with = ? It should be clear to everyone what's going on with +=, even if they don't know what , does. With = it's a little less intuitive. – John Doucette Jul 18 '11 at 05:08
  • 2
    If you are using `+=`, why not use `+`? (To be honest, I always hated this use of `+`, since it is not commutative... But that's what `std::string` does, and you are already doing it via `+=`, so why not be consistent?) – Nemo Jul 18 '11 at 05:13
  • 3
    Your assignment operator is *much* worse than your comma operator. – Dennis Zickefoose Jul 18 '11 at 05:16
  • This is just a simple example I came up with in 5 minutes. If I was going to use it in real code, I would add more functionality, error checking, and include it in my library's namespace, along other things. – Skyler Saleh Jul 18 '11 at 05:19
  • I'd just take a code snippet and show it to the two most junior people on your team and have them fix something important near the operator without any explanation except for the code. If they have to come to you and ask a question or take an inordinate amount of time it means you are wasting the teams time with this construct--if not it should be fine. – Bill K Jul 18 '11 at 18:36
  • --(operator overloading in general) – benzado Jul 18 '11 at 19:16

8 Answers8

40

Why not overload the << operator as QList does? Then use it like:

playlist.queue << song1 << song2; // The queue now contains song1 and song2
playlist.queue << song1 << song3 << song4; //The queue now contains two song1s and song2-4
rgngl
  • 5,353
  • 3
  • 30
  • 34
  • 18
    +1: Even if this is uglier on the eyes (which I'd argue it is), it at least has the precedent of other uses of shift operator overloading to mean "put an object somewhere" which started with iostreams. So I suppose it's idiomatic and less likely to be confusing to someone new to the code than overloading the comma operator. – Michael Burr Jul 18 '11 at 05:28
  • I was originally using that however, how would you handle list replacement, <<= made little sense. – Skyler Saleh Jul 18 '11 at 05:53
  • 5
    @RTS: I wouldn't. Simply assign to an empty list, or call a `clear` function, or something similar, and then resume insertions. – Dennis Zickefoose Jul 18 '11 at 06:06
  • 7
    @RTS: Steal another iostreams idea, manipulators: `playlist.queue << music_playlist::clear << song2;` – MSalters Jul 18 '11 at 08:15
  • @RTS: it's C++, you cannot arbitrarily create operators. I would suggest simply using `=` or a `reset` function ? – Matthieu M. Jul 18 '11 at 11:56
  • @Michael Burr: the `<<` convention has also been adopted in Ruby for adding things to arrays and strings. While that usage doesn't appear anywhere in the C++ standard library, it was certainly inspired by C++ iostreams. – Ken Bloom Jul 18 '11 at 13:53
20

I agree that your syntax looks nice as you have written it.
My main difficulty with the code is that I would expect the following to be the same

playlist.queue = song1,song2;
playlist.queue = (song1,song2);  //more of c-style, as @Iuser notes.

whereas in fact they are completely different.

This is dangerous because its too easy to introduce usage bugs into the code. If someone likes to use parenthesis to add extra emphasis to groupings (not uncommon) then the comma could become a real pain. For example,

//lets combine differnt playlists
new_playlist.queue =    song1        //the first playlist
                      ,(song3,song4) //the second playlist //opps, I didn't add song 3!
                      , song5;        //the third 

or

new_playlist.queue = (old_playlist.queue, song6); //opps, I edited my old playlist too!

Incidently, have you come across boost.assign: http://www.boost.org/doc/libs/1_47_0/libs/assign/doc/index.html

Tom
  • 5,219
  • 2
  • 29
  • 45
11

Has the precedence changed recently?

playlist.queue = song1,song2;

This should parse as:

(playlist.queue = song1) , song2;

Your ',' and '+=' are the same! It would be a better semantic match if your comma operator were to create a temporary list, insert the left and right items and return the temporary. Then you could write it like this;

playlist.queue = (song1,song2);

with explicit parens. That would give C-programmers a fighting chance at being able to read the code.

luser droog
  • 18,988
  • 3
  • 53
  • 105
  • 1
    You are correct as to how it parses, and the fact that it is identical to `operator +=`. It functions properly, so I'm not sure if his intent was to duplicate the two, or if he had something else in mind. Either way, your suggestion, while good in theory, places restrictions on the type of objects stored in the container... they have to overload `operator ,(T, T)` and it has to return the appropriate container type. This will almost never hapen in a general purpose container such as this, and can never happen with built in types. – Dennis Zickefoose Jul 18 '11 at 06:11
  • 1
    The OP did this on purpose. Again, I refer you to Boost.Assign which popularised this trick. Overloading `operator ,(T, T)` is *not* the way to go here. And the argument from C programmers is irrelevant. C and C++ are fundamentally different languages, users try to transfer knowledge at their own peril. If you make false assumptions about a language that’s your fault. – Konrad Rudolph Jul 18 '11 at 07:10
  • 1
    @Konrad: On the very last line, you can most probably substitute the `C-programmers` with a plain *other programmers*. I know that *boost.assign* uses this an overload of the comma operator, but it does it differently, it applies it to a type that is very specific to small pieces of code, where the damage is limited. That is, they don't overload `operator,(std::list,T)` but rather `operator,` applied to a type from which the objects are meant to be a one-time use (a proxy of sorts). `operator,` in a long lived object multiplies the chances of hard to find errors and it is hard to read. – David Rodríguez - dribeas Jul 18 '11 at 07:51
  • @Dennis, while I agree with what you say, it does not appear to me that this is supposed to be a "general purpose container". My guess is that 'list' is just a placeholder for a songQueue type awaiting a cool name. I suppose you'd need to implement all the various permutations: Q operator ,(Song, Song); Q operator ,(Song, Q); Q operator ,(Q, Song); Q operator ,(Q, Q); – luser droog Jul 19 '11 at 07:26
7

A bit of a problem is that if the compiler cannot choose your overloaded operator comma, it can fall back on using the built-in operator.

In contrast, with Boost.Assign mixing up types produces a compilation error.

#include <boost/assign.hpp>

int main()
{
    int one = 1;
    const char* two = "2";
    list<int> li;
    li = one, two;

    using namespace boost::assign;
    std::list<int> li2;
    li2 += one, two;
}
UncleBens
  • 40,819
  • 6
  • 57
  • 90
5

This is probably something that belongs over on Programmers, but here's my two cents.

If you're talking about code that has a fairly narrow context, where users will use it in a couple of places and that's all, then overloading the , operator is probably OK. If you're building a domain-specific language that is used in a particular domain and nowhere else, it's probably fine.

The issue comes when you're overloading it for something that you expect the user to use with some frequency.

Overloading , means that the reader needs to completely reinterpret how they read your code. They can't just look at an expression and instantly know what it does. You're messing with some of the most basic assumptions that C++ programmers make when it comes to scanning code.

Do that at your own peril.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
5

I am curious about whether I should avoid it, because above all the code should be easily understood by other programmers

If the goal is to make your code easy for other C++ programmers to understand, overriding operators to give them a meaning that's very different from that of standard C++ is not a good start. Readers shouldn't have to a) understand how you've implemented your container and b) recalibrate their understanding of standard operators just to be able to make sense of your code.

I can appreciate the Boost precedent for this sort of thing. If you're pretty sure that most of the people who will read your code will also be familiar with Boost Assign, your own override of operator, might be pretty reasonable. Still, I'd suggest following @badzeppelin's suggestion to use operator<< instead, just as iostreams does. Every C++ developer can be counted on to have run into code like:

cout << "Hello world!"`

and your append operation is very similar to writing to a stream.

Caleb
  • 124,013
  • 19
  • 183
  • 272
4

It's bad on so many levels...

You're overriding list and shadowingstd::list. A big no-no. If you want your own list class - make it be with a different name, don't shadow the standard library.

Using , in such way is not readable. The return value of the operator is the right operand. Even if your code works, for an external reader it won't be obvious why, and it's a bad thing. Code should be readable, not nice.

littleadv
  • 20,100
  • 2
  • 36
  • 50
  • I know that the original question is a very basic example. However in the actual application the audio decoding and the audio rendering happens on separate threads, out of necessity. So the final list modification needs to be mutex protected for fully defined behavior. The original implementation relied on the programmer to lock and unlock the mutex themselves. But often times it was forgotten leading to difficult to debug bugs. Then it was implemented using variadic functions in various flavors, this also led to hard to debug issues. So how would you implement the needed functionality? – Skyler Saleh Jul 18 '11 at 05:51
  • @RTS - how abusing the `operator ,` solves the problem of disciplining programmers or performing code review? – littleadv Jul 18 '11 at 06:03
  • because those undisciplined programmers are my customers. I know that this is utter operator abuse. So that is why I am asking for alternatives and opinions. – Skyler Saleh Jul 18 '11 at 06:12
  • @RTS - that would be enough for a whole different question here. Suggest you to post it, I promise to write up an answer. I think you'll be missing a lot by having it hidden in the comments. – littleadv Jul 18 '11 at 06:14
  • 4
    –1 from me. Overriding standard names is totally OK, that’s what namespaces are there for. Using the comma operator is problematic but Boost.Assign uses it for this exact purpose so there’s powerful precedence, and it’s in fact *very* readable. – Konrad Rudolph Jul 18 '11 at 07:07
  • @littleadv http://stackoverflow.com/questions/6729977/what-is-a-good-typesafe-alternative-to-variadic-functions-in-c , done – Skyler Saleh Jul 18 '11 at 07:50
2

There is nothing bad about using comma operator , using specifically. Any operator leaves bad taste, if exploited. In your code, I don't see any reasonable problem. Only one suggestion, I would like to give is:

list& operator,(list &add_){  // <--- pass by reference to avoid copies
  *this += add_;  // <--- reuse operator +=
  return *this;
}

This way, you have to always edit just operator +=, if you want any change in logic. Note that, my answer is in the perspective of readability and code maintenance in general. I will not raise concern about business logic you use.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • A non-const reference there would make the compiler use built-in `operator,` :) The whole thing works by creating temporary lists: `list, temporary_list, temporary_list`. – UncleBens Jul 18 '11 at 06:38
  • 2
    @iammilind: there are actually issues with the comma operator that you do not encounter with others. The real flaw, imho, is that `(s1, s2)` does something totally different from `f(s1,s2)`... In the presence of `operator()`, it can quickly get confusing. – Matthieu M. Jul 18 '11 at 11:59