4

I want to store a sequence of string in a queue. This seems pretty simple if i use the member function push()

queue test;
string s0("s0"), s1("s1");

test.push(s0);
test.push(s1);

I am thinking about adding the strings in the queue in implicitly way. That mean, if I type the following sequence of string the e.g. operator >> should push the string value in the queue.

queue test;
string s0("s0"), s1("s1");

s0 >> s1 >> s2 >> s3 ;

is there any way to do that?

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
sami
  • 467
  • 2
  • 6
  • 11
  • 3
    I fixed your code formatting for you. This is not done with [CODE] tags, but by indenting the line by four spaces (clicking the code button above the text field will do that for you). – Björn Pollex Sep 02 '10 at 11:10

6 Answers6

16

While C++ doesn't allow you this, it allows you to do something very similar: test << s0 << s1; However, don't do this!

If I see test.push(s0), I know exactly what it does, without even looking at the type of test. If I see test << s0 << s1;, I'd think test is a stream that's written to.


Here's my three basic rules you should stick to when overloading operators:

  1. Despite seemingly obvious contrary evidence, there only are surprisingly few cases where operator overloading is appropriate. Consequentially, the first and foremost rule for overloading operators, at its very heart, says: Don’t do it. That might seem strange, but the reason is that actually it is hard to understand the semantics behind the application of an operator unless the use of the operator in the application domain is well known and undisputed. Contrary to popular believe, this is hardly ever the case. Whenever the meaning of an operator is not obviously clear and undisputed, it should not be overloaded. Instead, provide a function with a well-chosen name.
  2. C++ poses few limitations on the semantics of overloaded operators. Your compiler will happily accept code that implements the binary + operator to change its right operand. However, the users of such an operator would never suspect the expression a + b to change the value of b. This is why the second rule of operator overloading says: Always stick to the operator’s well-known semantics. (This, in turn, presumes that the semantics are undisputed; see the previous rule.)
  3. Finally, always remember that operators are related to each other and to other operations. If your type supports a + b, users will expect to be able to call a += b, too. If it supports prefix increment ++a, they will expect a++ to work, too. If they can check whether a < b, they will most certainly expect to also to be able to check whether a > b. If they can copy-construct your type, they expect assignment to work as well. So the third rule of operator overloading reminds you: Always provide all out of a set of related operations.

As with all such rules, there are indeed exceptions. Sometimes people have deviated from them and the outcome was not bad code, but such deviations are few and far between. At the very least, 99 out of 100 such deviations I have seen were unjustified. However, it might just as well have been 999 out of 1000. So you’d better stick to these rules.

So just in case I hadn't been clear enough: For the code from your question, a deviation from these rules is very bad.

sbi
  • 219,715
  • 46
  • 258
  • 445
  • You should call up Nokia/Trolltech, and let them know that code using Qt is unreadable ;-p `<<` meaning "add to collection" is less clear and undisputed than `<<` meaning "write to stream", but it's there or thereabouts. Trying to bolt that meaning onto the standard library in user code is what I find questionable... – Steve Jessop Sep 02 '10 at 11:52
  • If the Standard defines << as insertion and >> as extraction, then it is absolutely as clear and undisputed as you can possibly get, since it's literally written in the rules of the language. I think that << and >> as insert/extract from more containers than just streams would be OK. – Puppy Sep 02 '10 at 12:01
  • @DeadMG: It's certainly not written in the rules of the language that streams are containers - they aren't. I don't think it's a massive leap from "stream insertion" to "add to collection", but I also don't think it's the same thing. – Steve Jessop Sep 02 '10 at 12:10
  • Insert into stream, insert into collection. It is the same thing. – Puppy Sep 02 '10 at 12:30
  • @DeadMG: it depends on the collection, I expect `>>` to return the last element inserted with `<<`, how do you do that with a `set` ? – Matthieu M. Sep 02 '10 at 13:04
  • @Matthieu: I never said that >> and << both make sense for all containers, all of the time. But, for a queue (or perhaps priority queue), they could easily make sense. vector/list, maybe. The associative containers, probably not. – Puppy Sep 02 '10 at 13:10
  • @DeadMG: for a queue/stack I would understand, for vector/list I agree it's hazy because you can insert / retrieve from both ends. – Matthieu M. Sep 02 '10 at 15:36
  • @Steve: If some vendor of a popular lib managed to push something like this, they might bend rule #1. This is, after all, what Jerry Schwarz did when he abused `<<` and `>>` for his streams and what the standard committee did with `+` for its `string`. (Where `a+b` is different than `b+a` - oh the horror!) Now this is established praxis. However, when you, as one developer, and a newbie at that, do this, it is _very_ unlikely that you are able to bend rule #1. – sbi Sep 02 '10 at 19:32
  • At my place of work, the `iostream` library's abuse of the `<<` and `>>` operators was one of several reasons why we avoid `iostream`. We do a lot of `printf("ISR = %08x\n", isr);` kind of lines, which turn into `cout << "ISR = " << hex << uppercase << setw(8) << isr << dec << lowercase << endl;`, which turns into a lot more code (we do embedded work) and becomes much harder to read if things get more complex. We take care of printing objects by requiring them to provide a `void Print(FILE*) const;` method or a `std::string Str() const;` method. In short, `iostream` isn't worth the trouble. – Mike DeSimone Oct 18 '10 at 19:59
  • @Mike: I'm one of the probably very few people who do C++ for >15 years and yet have learned C++ before I learned C. I can tell you that there's nothing inherently more graspable in formatting strings than in the iostreams' formatting. When I see `std::hex << std::uppercase << std::setw(8)` I know exactly what's happening, while those `printf()` formatting strings always look like disguised swear words. I freely admit that streams are lacking in design (mixing buffering and device read/write? no std way to reset formatting?), but the only advantage of `printf()` is 40 years of indoctrination. – sbi Oct 18 '10 at 20:32
  • I'm one of those people as well (I learned BASIC, Assembly, Pascal, C++, C, and Python in that order), and I'll admit saying "harder to read" was a poor choice. "Harder to maintain" (we say in three lines what `printf` does in one, which means less code in a function if you follow the two-screen rule) or "harder to debug" (love it when someone doesn't restore format state and you can't tell who) would have been better. Honestly, my main beefs are 1) formatting shouldn't be stateful, and 2) every little thing turns into a function call if the library writer wasn't careful. – Mike DeSimone Oct 18 '10 at 22:23
  • It makes me wonder if Boost has an iostreams replacement that does it right. Maybe by divorcing I/O from formatting? – Mike DeSimone Oct 18 '10 at 22:24
  • @Mike: I, too, hate that there isn't any standard way to reset formatting. It's easy to come up with a solution that works in 98% of all cases, but a real bullet-proof method is pretty darn hard to do. I don't mind spelling out things explicitly. (You might have noticed I'm passionate of [always spelling out all namespace prefixes](http://stackoverflow.com/questions/2879555/2880136#2880136).) I/O is already divorced from formatting with the streams lib, but it's married to buffering. I haven't looked at what boost has to offer for a long time (forced to do C#), so I wouldn't know. – sbi Oct 19 '10 at 07:27
  • Oh, and I see that I forgot the main drawback of `printf()` compared to streams: Errors might easily lead to invoking UB with a pretty good chance of undetected run-time errors - that's about as bad as things can get. – sbi Oct 19 '10 at 07:29
  • I take care of prefixes as well, but there's a 600 character limit on comments. And I wrote a subclass of `streambuf` a decade ago (basically a `cerr` replacement that was threadsafe and allowed runtime redirection), so I get "married to buffering"; I just consider buffering more related to I/O than formatting. Also, don't take me for a fan of `printf` (what is UB? Unbuffered?), but I've been looking for something at least as good as Python's `str.format`. No luck yet. – Mike DeSimone Oct 19 '10 at 13:29
3

First off, your example of 'implicitly' enqueueing items has no mention of the queue - do you mean something like:

test << s0 << s1 << s2 << s3;

If so, it's possible, but I wouldn't recommend it. It really doesn't help readability that much. If you really do want it, though, put this in a header somewhere and include it wherever you want this behavior:

template<typename T>
std::queue<T> &operator<<(std::queue<T> &q, const T &v)
{
    q.push(v);
    return q;
}

Note that the opposite order - s0 >> s1 >> s2 >> test - is not possible, due to C++ precedence rules.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • 2
    Considering that `T==std::string`, I'd get a bit nervous about Argument Dependent Lookup (Koenig Lookup). – MSalters Sep 02 '10 at 11:24
  • 1
    MSalters is right, for ADL to work appropriately when called from a different namespace than that where the operator is defined, the operator should be defined in the `std` namespace, but that is not allowed in the standard (specializations of existings functions/algorithms is allowed, but adding new operations is not). – David Rodríguez - dribeas Sep 02 '10 at 11:38
  • why it is not allowed to implement new operator in c++. I may think about implementing the operator "=>". technically, What is forbidden in c++ to implement such sequence of string s0 => s1 => s2 => ..... And the operator shall store those string in the queue? – sami Sep 02 '10 at 11:47
  • @sami: Probably because it would make it very hard (and potentially impossible) for the compiler to unambiguously parse your code. Also, the whole point of standard operator overloading is to make complex types "look and feel" like primitive types (e.g. matrix addition looks like scalar addition). Allowing user-defined operators doesn't help here! – Oliver Charlesworth Sep 02 '10 at 11:55
  • @Oli: I don't think that's the *whole* point of operator overloading. If it were, then streams would not use `operator>>` and `operator<<` as they do. But I agree that the reason C++ doesn't let you invent operators is that it would interfere with the rules for parsing expressions. In fact, even tokenization could become difficult, as shown by the space-needed-in-`vector >` issue. When you invented an operator, you'd have to also supply rules for (1) tokenisation *in the preprocessor and compiler* and (2) operator precedence, so allowing this would require a lot of extra machinery. – Steve Jessop Sep 02 '10 at 12:02
  • @Steve: That's a fair counterexample! Substitute "in general" for "the whole point"... – Oliver Charlesworth Sep 02 '10 at 12:07
  • @sami Some languages, such as Haskell, do actually allow defining new operators - however, C++ is already a nightmare to parse without allowing people to define their own operators. – bdonlan Sep 02 '10 at 12:09
3

I would not really do it, but if you really feel that you need to provide that syntax, you can write an inserter adapter...

template <typename C>
class inserter_type {
public:
   typedef C container_type;
   typedef typename container_type::value_type value_type;

   explicit inserter_type( container_type & container ) : container(container) {}
   inserter_type& operator<<( value_type const & value ) {
      container.push( value );
      return *this;
   }
private:
   container_type & container;
};
template <typename C>
inserter_type<C> inserter( C & container ) {
   return inserter_type<C>(container);
}
int main() {
   std::queue<std::string> q;
   inserter(q) << "Hi" << "there";
}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
2

Qt containers work exactly like this:

QStringList list;
list << "Sven" << "Kim" << "Ola";

QVector<QString> vect = list.toVector();
// vect: ["Sven", "Kim", "Ola"]

If you want the same to work with STL containers, you would need to write the operator overloading yourself but obviously you can't add operations to std namespace.

teukkam
  • 4,267
  • 1
  • 26
  • 35
  • Easier said than done... you are not allowed to break into the `std` namespace to add new operators (and there is no operator taking a `queue` as first argument in the standard), and defining the operator outside of the `std` namespace can be tricky with ADL. – David Rodríguez - dribeas Sep 02 '10 at 11:43
  • @David: You could always implement that operator in the global namespace. – sbi Sep 05 '10 at 22:29
0

If you wanted to do this it would typically be expressed as

test << s0 << s1 << s2 << s3;

by suitable definition of overloaded operator<< on the queue class. I don't know why this would be better than a simple series of test.push() calls though.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • Because it's more readable when adding a bunch of items at once and it's familiar from stream usage. – teukkam Sep 02 '10 at 11:17
  • yes, I had this thoughts. But I do not want to implement it that way. Imagine you have a couple of string that should be added in the queue. The user should not necessarily know about the mechanism that works behind it. I typed the operator >> just for example but it could be another operator. Maybe =>. Could I implement such operator "=>" which reads the link value of the string's sequence and stores it in the queue. – sami Sep 02 '10 at 11:19
0

I agreed with most answers, which told you that you could do it, as long as you included the queue object, and I also agree that readability is compromised since it's a very non-standard behavior.

Still, I was wondering. What if you try something like:

queue test;
string s0("s0"), s1("s1");

test.push(s0).push(s1);

That could be implemented very simply, would still give you the correct readability (since the meaning of push is well understood), and keep your code concise (which seems to be your main objective).

To implement it, all you'd have to do is extend the Queue class (or write your own queue class which would, in turn, wrap around an STL Queue object).

Bruno Brant
  • 8,226
  • 7
  • 45
  • 90
  • Bad idea. `std::queue` is defined in the standard, and the `push` method **does not** return a reference to the queue, so you cannot perform the method chaining. Also note that while it would be technically possible to edit the standard headers to provide support for this, you would be breaking a standard compliant implementation into a non-portable non-standard library, which cannot be considered a good idea ever. – David Rodríguez - dribeas Sep 02 '10 at 11:41