No cost streams

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.

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:

lcd.print("GPS #");
lcd.print(gpsno);
lcd.print(" date: ");
lcd.print(day);
lcd.print("-");
lcd.print(month);
lcd.print("-");
lcd.print(year); // ugh!!

It occurred to me that adding 7 short lines to Print.h:

...
    /* 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:

lcd << "GPS #" << gpsno << " date: " << day << "-" << month << "-" << year;

It works for all Print derivatives:

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?

Mikal

Nice!

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

-j

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

class Print
{
  private:
    void printNumber(unsigned long, uint8_t);
  public:
    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.

Mikal

Works a treat.

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

  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:

  Serial << (number, BYTE) << ", dec: " << number << ", hex: " << (number, HEX) << ", oct: " << (number, OCT) << ", bin: " << (number, BIN);
  Serial.println();

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.

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.

@mellis:

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,

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! :slight_smile:

@mem:

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

Serial << (9, HEX); //ignores 9 and prints 16 -- the value of HEX

is not the same thing as

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.

Mikal

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

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

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:

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,

Mikal

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" ??)

"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 :slight_smile:

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 :slight_smile:

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.

Mikal

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: Arduino Playground - 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!

Mikal

Cool. But please do post this to the playground (or somewhere). Because while it's good to have a small, simple, consistent core, it's also good to have an varied, easily accessible selection of more advanced (or just different) things for people to use. I added a reference to the playground page from the Reference in the hopes that it would make it easier for people to find and share useful code that doesn't necessarily fit in the core. Other suggestions for helping capture this sort of thing are very welcome.

Done! Arduino Playground - StreamingOutput

Referred to by the code library: Arduino Playground - GeneralCodeLibrary

Thank you, mellis.

Mikal

Hum, I tried to use this command at the top of my sketch:

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

But it gives me this error:

error: expected initializer before '&' token

I'm not using the Arduino core, but the Sanguino one (based on the ATmega 644p). Do you have any idea where the problem can be? I'm not quite used to this level of programming...

Can you post your whole sketch? It must be something about the context. The Print class wasn't supported until Arduino 0012; it's probably not available in your version.

Mikal

The sketch itself is empty (just to test the compilation process).

I'm using Arduino 12 IDE, with this core:

http://frankyfuzzfire.free.fr/DIY/Arduino/Sanguino.zip

I tried with the classic Arduino core (ATmega 168), the sketch compiles well. It seems that it's just this core that does not work.