If you try to use a natural way of printing:
3.141592653589793testtrue12016.09.14 11:48:24clrRed
Print(DoubleToString(M_PI, 5), " test ", boolean, " ", day, " ", TimeToString(dt, TIME_DATE | TIME_MINUTES), " ", clr);
Of course one can invent some shorthands, such as a popular DS(number, digits) macro for DoubleToString, but they do not actually solve the problem, but make it less obtrusive.
First start the class and its private member variables.
class OutputStream { private: int digits; // default number of digits for floating point numbers char delimiter; // default separator between values int timemode; // default format for datetime string format; // format string for numbers with floating point string line; // internal buffer, where all printed data is accumulated
void createFormat() { format = "%." + (string)digits + "f"; }
public: OutputStream(): digits(_Digits), timemode(TIME_DATE|TIME_MINUTES) { createFormat(); } OutputStream(int p): digits(p), timemode(TIME_DATE|TIME_MINUTES) { createFormat(); } OutputStream(int p, char d): digits(p), delimiter(d), timemode(TIME_DATE|TIME_MINUTES) { createFormat(); } OutputStream(int p, char d, int t): digits(p), delimiter(d), timemode(t) { createFormat(); }
Default value for digits (if it's not specified) is the _Digits of current _Symbol. Default datetime format is without seconds. Default separator is empty.
template<typename T> OutputStream *operator<<(const T v) { if(typename(v) == "int" || typename(v) == "uint" || typename(v) == "long" || typename(v) == "ulong" || typename(v) == "short" || typename(v) == "ushort") { if(typename(v) == "ushort" && v == '\n') { Print(trimmedLine()); line = NULL; return GetPointer(this); } else { line += IntegerToString((int)v); } } else if(typename(v) == "double" || typename(v) == "float") { if(v == EMPTY_VALUE) line += "<EMPTY_VALUE>"; else line += StringFormat(format, v); } else if(typename(v) == "datetime") { line += TimeToString((datetime)v, timemode); } else if(typename(v) == "color") { line += (string)v; } else if(typename(v) == "char" || typename(v) == "uchar") { if(v == '\n') { Print(trimmedLine()); line = NULL; return GetPointer(this); } else { line += CharToString((char)v); } } else { line += (string)v; } if(delimiter != 0) { line += CharToString(delimiter); } return GetPointer(this); }
In this case it's implemented as a single templatized method which can accept value of any built-in type. Instead of this you could define multiple methods with different parameter types - one per every built-in type, but I decided the single universal one. In this method we process passed variable according to its type determined by typename function. For every type, specific API function - such as DoubleToString, TimeToString, CharToString - is used to get its string representation, which is added to the line member variable. The process uses predefined settings specified only once in constructor. When the input contains single newline character '\n', all the contents of the line varibale is flushed by Print call and the line is emptied. Printing uses a private method trimmedLine, which cuts latest separator. Separators are added automatically after every streamed value, because we assume a next value to come, but the latest separator makes no sense. Thus this method is required. Alternatively one could insert separator before every new value except very first one, but then you should check if the line is empty before every concatenation.
Now we can create an object of this class.
And then use it in the following way
which outputs
The EN helper is a shorthand for enumeration printing.
One important thing to note is that operator << defined above can process only built-in types. For objects we should add another templates.
template<typename T> OutputStream *operator<<(const T &v) { line += typename(v) + StringFormat("%X", GetPointer(v)); if(delimiter != 0) { line += CharToString(delimiter); } return GetPointer(this); } template<typename T> OutputStream *operator<<(const T *v) { line += typename(v) + StringFormat("%X", v); if(delimiter != 0) { line += CharToString(delimiter); } return GetPointer(this); }
The type T here is resolved to any object - they are passed by reference or as pointers. This implementation prints out not so much information about the objects. If you want objects to provide additional information, you can define a class Printable like that
class Printable { public: virtual string toString() const { return "<?>"; }; };
Please, use recent versions of MQL compiler to eliminate possible errors which may arise on older versions. MQL is constantly extending its support of more and more OOP features.