Go Down

Topic: No cost streams (Read 11408 times) previous topic - next topic


Jan 06, 2009, 07:53 am Last Edit: Jan 06, 2009, 08:05 am by mikalhart Reason: 1
New users sometimes wonder why the "Arduino language" doesn't provide the kind of concatenation or streaming operations they have become accustomed to in Java/VB/C#/C++, etc.
Code: [Select]
Serial.print("The button was pressed " + counter + " times."); // newbie q: why doesn't this work?
I myself chafe at having to synthesize streams with clumsy blocks of repetitive code like this:
Code: [Select]
lcd.print("GPS #");
lcd.print(" date: ");
lcd.print(year); // ugh!!

It occurred to me that adding 7 short lines to Print.h:
Code: [Select]
   /* new inline members of base class Print */
   inline Print &operator <<(char arg) {print(arg); return *this;}
   inline Print &operator <<(const char *arg) {print(arg); return *this;}
   inline Print &operator <<(uint8_t arg) {print(arg); return *this;}
   inline Print &operator <<(int arg) {print(arg); return *this;}
   inline Print &operator <<(unsigned int arg) {print(arg); return *this;}
   inline Print &operator <<(long arg) {print(arg); return *this;}
   inline Print &operator <<(unsigned long arg) {print(arg); return *this;}

would give users the option of writing "insertion style" code like this:
Code: [Select]
lcd << "GPS #" << gpsno << " date: " << day << "-" << month << "-" << year;
It works for all Print derivatives:
Code: [Select]
Serial << "Counter: " << counter;
lcd << "Temp: " << t.get_temperature() << " degrees";

This syntax is familiar to many, is easy to read and learn, and, importantly, consumes no resources.  (Because the operator functions are essentially just inline aliases for their print() counterparts, no sketch gets larger or consumes more RAM as a result of their inclusion.)

Who would support providing these in a future revision of Print?




I don't do much in C++, but since I can't have printf() this looks good to me.



Thanks, KG!

If anyone would like to try this out, all you have to do is change the definition of the Print class in hardware/cores/Arduino/Print.h to

Code: [Select]
class Print
   void printNumber(unsigned long, uint8_t);
   virtual void write(uint8_t);
   void print(char);
   void print(const char[]);
   void print(uint8_t);
   void print(int);
   void print(unsigned int);
   void print(long);
   void print(unsigned long);
   void print(long, int);
   void println(void);
   void println(char);
   void println(const char[]);
   void println(uint8_t);
   void println(int);
   void println(unsigned int);
   void println(long);
   void println(unsigned long);
   void println(long, int);

   inline Print &operator <<(char arg) {print(arg); return *this;}
   inline Print &operator <<(const char *arg) {print(arg); return *this;}
   inline Print &operator <<(uint8_t arg) {print(arg); return *this;}
   inline Print &operator <<(int arg) {print(arg); return *this;}
   inline Print &operator <<(unsigned int arg) {print(arg); return *this;}
   inline Print &operator <<(long arg) {print(arg); return *this;}
   inline Print &operator <<(unsigned long arg) {print(arg); return *this;}

I don't think you'll even have to recompile anything to get it working, since it's all inline.



Jan 06, 2009, 08:15 pm Last Edit: Jan 06, 2009, 08:17 pm by mem Reason: 1
Works a treat.

Here is a fragment from the communications example sketch, asciiTable.pde:

Code: [Select]
 Serial.print(number, BYTE);    // prints value unaltered, first will be '!'
 Serial.print(", dec: ");
 Serial.print(number);          // prints value as string in decimal (base 10)
 Serial.print(", hex: ");
 Serial.print(number, HEX);     // prints value as string in hexadecimal (base 16)
 Serial.print(", oct: ");
 Serial.print(number, OCT);     // prints value as string in octal (base 8)
 Serial.print(", bin: ");
 Serial.println(number, BIN);   // prints value as string in binary (base 2)

And the same functionality using inserters:
Code: [Select]
 Serial << (number, BYTE) << ", dec: " << number << ", hex: " << (number, HEX) << ", oct: " << (number, OCT) << ", bin: " << (number, BIN);

Both versions produce the same output, the inserter version uses 46 less bytes of program memory.

Nice one!


I'm hoping to add a string library / class at some point which should (I hope) provide concatenation capabilities.  I think that might be more elegant than introducing the << operator.


Jan 06, 2009, 08:19 pm Last Edit: Jan 06, 2009, 08:35 pm by mem Reason: 1
But will it be as frugal with RAM and program memory?

A string library will be useful, but so will having the inserters added to print.



Having written string class libraries for PC platforms in the past, I briefly considered what it would take to write one for Arduino, but came to the conclusion -- and please correct me if I'm wrong -- that Arduino's tight 1K memory space would make it impossible to write one that was both useful and robust.

I assume you envision each string object having a dynamically allocated buffer?  What happens when the allocation fails?  Simple concatenations, for example,

Code: [Select]
string str = "Counter value is: ";
str += counter;
str += " rpms";

consume lots of extra RAM, at least temporarily.

Or do you propose having each string object having its own static buffer?  This has its shortcomings too, of course.

I would LOVE to be proven misguided on this! :)


I appreciate the positive feedback but am sorry to say that your example doesn't in fact work correctly, because

Code: [Select]
Serial << (9, HEX); //ignores 9 and prints 16 -- the value of HEX
is not the same thing as
Code: [Select]
Serial.print(9, HEX); // prints 9 in HEX format

The insertion operators, alas, do not work with the modifiers OCT, HEX, etc., and I don't think there is any way that using them can actually reduce the size of your code.



I'd like to see this get implemented. It seems like an elegant memory solution at the expense of some perhaps clunky code.


Jan 11, 2009, 07:33 am Last Edit: Jan 11, 2009, 08:51 am by mikalhart Reason: 1
Thank you, paulb.

This week I realized that even if streaming doesn't get into Print.h, you can get all the benefit by adding just a single line at the top of your sketch (or any header):

Code: [Select]
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; }

This one (admittedly ugly) line allows you to write code like:

Code: [Select]
Serial << "My name is " << name << " and I am " << age << " years old.";

I'm afraid my proposal to have this included in the system is not likely to go very far.  On the developer's forum, Nicholas Zambetti noted that as Arduino is "part of the Processing family", we "should be mindful to adopt only technically and stylistically compatible syntax".

Fair enough!

All the best,



I don't think I quite understand the syntax.  Are you choosing to override the existing "<<" operator to cause immediate function execution (and in that case could some other operator be uses as well?)  Or is "<<" already magical in some way ("streaming" ??)


Jan 11, 2009, 01:52 pm Last Edit: Jan 11, 2009, 01:54 pm by dcb Reason: 1
"adopt only technically and stylistically compatible syntax"
Well, if we were to take that literally then we should be doing chip code in java, or processing :)

cout<< was one of the things I did like about C++, and I always liked  function chaining since the smalltalk days.  I think it is an interesting example you have there.   Returning void all the time doesn't do anyone any good (unless you are optimizing).  Return self at least.

If lines upon lines of print statements are "stylistic", then, well, they aren't, so never mind :)


Jan 11, 2009, 08:37 pm Last Edit: Jan 11, 2009, 08:41 pm by halley Reason: 1
I don't think I quite understand the syntax. ... Or is "<<" already magical in some way ("streaming" ??)

When C++ started supporting "overloaded operators," the first bit of wisdom was to say, don't overload an operator with something completely different from its original intent.  A Vector plus a Vector should use the operator + to add them.

But then C++ creator Bjarne Stroustrup (or "Barney Shoestrap" as some like to say) broke this sensible wisdom in a big way with operator << and operator >>.  He didn't use them for shifting, but for streaming.  It's a love/hate thing with C++ programmers.  Some love it, some absolutely loath it.

The usage mikalhart shows is quite common:  it lets you bang a bunch of things through the various print() methods in sequence, using only one expression.  I think his idea to include it in the Print header makes a lot of sense, even if I'm not too warm on the syntax.

If print() returned a reference, *this, you could do the same with Serial.print(x).print(y).print(z), but that's not obvious either.


...unless you are optimizing...

@dcb: Part of the beauty of it is that the "return self" stuff IS optimized out!  It's no different at runtime than the series of Serial.prints.  

@halley: I think I read a Stroustrup interview once in which tried to justify '<<' as the best choice among all available left-to-right binding operators.  You might have preferred something like
Serial += "Age: " += age;
but because += binds right-to-left it would print things backwards.

Thanks, all, for the comments.



mikalhart: I really like this addition, but I'm not sure it makes sense in the Arduino core.  I would encourage you to document it on the Code library page on the playground: http://www.arduino.cc/playground/Main/GeneralCodeLibrary as I'm sure there are a lot of people who would find it useful.


I am coming around to this point of view myself, mellis.  I appreciate Massimo's comment (on the developers' list) that we try to keep the core as simple as possible and use the libraries for everything else.

Thanks for the comments!


Go Up