Pages: [1] 2 3 4   Go Down
Author Topic: No cost streams  (Read 4991 times)
0 Members and 1 Guest are viewing this topic.
Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
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:
Code:
...
    /* 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:
lcd << "GPS #" << gpsno << " date: " << day << "-" << month << "-" << year;
It works for all Print derivatives:
Code:
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
« Last Edit: January 06, 2009, 02:05:21 am by mikalhart » Logged

0
Offline Offline
Faraday Member
**
Karma: 8
Posts: 2526
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Nice!

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

-j

Logged

Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Works a treat.


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

Code:
 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:
 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!
« Last Edit: January 06, 2009, 02:17:07 pm by mem » Logged

Forum Administrator
Cambridge, MA
Offline Offline
Faraday Member
*****
Karma: 12
Posts: 3538
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
« Last Edit: January 06, 2009, 02:35:42 pm by mem » Logged

Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@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,

Code:
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! smiley


@mem:

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

Code:
Serial << (9, HEX); //ignores 9 and prints 16 -- the value of HEX
is not the same thing as
Code:
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
Logged

0
Offline Offline
Sr. Member
****
Karma: 0
Posts: 267
dinosaur cork
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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



Logged

Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
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
« Last Edit: January 11, 2009, 02:51:21 am by mikalhart » Logged

SF Bay Area (USA)
Offline Offline
Tesla Member
***
Karma: 133
Posts: 6749
Strongly opinionated, but not official!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

0
Offline Offline
God Member
*****
Karma: 1
Posts: 513
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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 smiley
« Last Edit: January 11, 2009, 07:54:09 am by dcb » Logged

Connecticut, US
Offline Offline
Edison Member
*
Karma: 2
Posts: 1036
Whatduino
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
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.
« Last Edit: January 11, 2009, 02:41:55 pm by halley » Logged

Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Logged

Forum Administrator
Cambridge, MA
Offline Offline
Faraday Member
*****
Karma: 12
Posts: 3538
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Austin, TX USA
Offline Offline
God Member
*****
Karma: 4
Posts: 997
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Pages: [1] 2 3 4   Go Up
Jump to: