diff --git a/cores/arduino/Print.cpp b/cores/arduino/Print.cpp index 1e4c99a65..86cbc98b1 100644 --- a/cores/arduino/Print.cpp +++ b/cores/arduino/Print.cpp @@ -41,173 +41,55 @@ size_t Print::write(const uint8_t *buffer, size_t size) return n; } -size_t Print::print(const __FlashStringHelper *ifsh) +size_t Print::println(void) +{ + return write("\r\n"); +} + + +size_t Formatters::DefaultFormatter::printTo(Print *p, const __FlashStringHelper *ifsh) const { - PGM_P p = reinterpret_cast(ifsh); + PGM_P ptr = reinterpret_cast(ifsh); size_t n = 0; while (1) { - unsigned char c = pgm_read_byte(p++); + unsigned char c = pgm_read_byte(ptr++); if (c == 0) break; - if (write(c)) n++; + if (p->write(c)) n++; else break; } return n; } -size_t Print::print(const String &s) -{ - return write(s.c_str(), s.length()); -} - -size_t Print::print(const char str[]) -{ - return write(str); -} - -size_t Print::print(char c) -{ - return write(c); -} - -size_t Print::print(unsigned char b, int base) -{ - return print((unsigned long) b, base); -} - -size_t Print::print(int n, int base) -{ - return print((long) n, base); -} - -size_t Print::print(unsigned int n, int base) +size_t Formatters::DefaultFormatter::printTo(Print *p, const String &s) const { - return print((unsigned long) n, base); + return p->write(s.c_str(), s.length()); } -size_t Print::print(long n, int base) +size_t Formatters::DefaultFormatter::printSignedNumber(Print *p, signed long n) const { - if (base == 0) { - return write(n); - } else if (base == 10) { + if (this->base == 10) { if (n < 0) { - int t = print('-'); + size_t t = p->write('-'); n = -n; - return printNumber(n, 10) + t; + return printNumber(p, n) + t; } - return printNumber(n, 10); + return printNumber(p, n); } else { - return printNumber(n, base); + return printNumber(p, n); } } -size_t Print::print(unsigned long n, int base) -{ - if (base == 0) return write(n); - else return printNumber(n, base); -} - -size_t Print::print(double n, int digits) -{ - return printFloat(n, digits); -} - -size_t Print::println(const __FlashStringHelper *ifsh) -{ - size_t n = print(ifsh); - n += println(); - return n; -} - -size_t Print::print(const Printable& x) -{ - return x.printTo(*this); -} - -size_t Print::println(void) -{ - return write("\r\n"); -} - -size_t Print::println(const String &s) -{ - size_t n = print(s); - n += println(); - return n; -} - -size_t Print::println(const char c[]) -{ - size_t n = print(c); - n += println(); - return n; -} - -size_t Print::println(char c) -{ - size_t n = print(c); - n += println(); - return n; -} - -size_t Print::println(unsigned char b, int base) -{ - size_t n = print(b, base); - n += println(); - return n; -} - -size_t Print::println(int num, int base) -{ - size_t n = print(num, base); - n += println(); - return n; -} - -size_t Print::println(unsigned int num, int base) -{ - size_t n = print(num, base); - n += println(); - return n; -} - -size_t Print::println(long num, int base) -{ - size_t n = print(num, base); - n += println(); - return n; -} - -size_t Print::println(unsigned long num, int base) -{ - size_t n = print(num, base); - n += println(); - return n; -} - -size_t Print::println(double num, int digits) -{ - size_t n = print(num, digits); - n += println(); - return n; -} - -size_t Print::println(const Printable& x) -{ - size_t n = print(x); - n += println(); - return n; -} - // Private Methods ///////////////////////////////////////////////////////////// -size_t Print::printNumber(unsigned long n, uint8_t base) +size_t Formatters::DefaultFormatter::printNumber(Print *p , unsigned long n) const { char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. char *str = &buf[sizeof(buf) - 1]; + uint8_t base = this->base; *str = '\0'; - // prevent crash if called with base == 1 + // prevent crash if called with base 0 or 1 if (base < 2) base = 10; do { @@ -217,22 +99,27 @@ size_t Print::printNumber(unsigned long n, uint8_t base) *--str = c < 10 ? c + '0' : c + 'A' - 10; } while(n); - return write(str); + while (str > buf && buf + sizeof(buf) - 1 - str < this->min_width) + *--str = '0'; + + return p->write(str); } -size_t Print::printFloat(double number, uint8_t digits) +size_t Formatters::DefaultFormatter::printFloat(Print *p, double number) const { size_t n = 0; + uint8_t digits = this->precision; + auto int_formatter = DefaultFormatter(); - if (isnan(number)) return print("nan"); - if (isinf(number)) return print("inf"); - if (number > 4294967040.0) return print ("ovf"); // constant determined empirically - if (number <-4294967040.0) return print ("ovf"); // constant determined empirically + if (isnan(number)) return p->write("nan"); + if (isinf(number)) return p->write("inf"); + if (number > 4294967040.0) return p->write ("ovf"); // constant determined empirically + if (number <-4294967040.0) return p->write ("ovf"); // constant determined empirically // Handle negative numbers if (number < 0.0) { - n += print('-'); + n += p->write('-'); number = -number; } @@ -246,11 +133,11 @@ size_t Print::printFloat(double number, uint8_t digits) // Extract the integer part of the number and print it unsigned long int_part = (unsigned long)number; double remainder = number - (double)int_part; - n += print(int_part); + n += int_formatter.printTo(p, int_part); // Print the decimal point, but only if there are digits beyond if (digits > 0) { - n += print('.'); + n += p->write('.'); } // Extract digits from the remainder one at a time @@ -258,7 +145,7 @@ size_t Print::printFloat(double number, uint8_t digits) { remainder *= 10.0; unsigned int toPrint = (unsigned int)(remainder); - n += print(toPrint); + n += int_formatter.printTo(p, toPrint); remainder -= toPrint; } diff --git a/cores/arduino/Print.h b/cores/arduino/Print.h index 058a2abbd..d9c71df90 100644 --- a/cores/arduino/Print.h +++ b/cores/arduino/Print.h @@ -34,17 +34,60 @@ #endif #define BIN 2 +#define _always_inline __attribute__ ((__always_inline__)) // undefined at end + + +// Namespace to hide implementation details into. Its contents should +// not be used outside of this file. +namespace detail { + // Returns a value of the given type. No implementation, so this is + // meant for use in decltype only. Identical to std::declval. + template T declval(); + + // Function that accepts a pointer of the given type. Can be used to + // detect if another type is convertible to T. No implementation, so + // intended to be used in declval only. + template + void accepts_pointer_of_type(const T*); + + // Simplified and integrated version of std::enable_if_t and + // std::is_base_of (which are not available on AVR). Integrating them + // makes things more specific and thus simpler. + // This type alias resolves to `void` when `Base` is a base class of + // `Derived`, or fails to resolve otherwise. + // TODO: This check does not detect private base classes. To do so, a + // more complicated check is needed, something like + // https://stackoverflow.com/questions/2910979/how-does-is-base-of-work + template + using enable_if_base_of = decltype(accepts_pointer_of_type(declval())); +} + +/* Forward declarations */ +namespace Formatters { + template + class OptionList; + + class DefaultFormatter; +} + +// Forward declaration +template +Formatters::DefaultFormatter DefaultFormatterFor(T); + class Print { private: int write_error; - size_t printNumber(unsigned long, uint8_t); - size_t printFloat(double, uint8_t); protected: void setWriteError(int err = 1) { write_error = err; } public: Print() : write_error(0) {} + // TODO: Move these into the Formatters namespace? + // TODO: Shorten / rethink these names? + struct Formatter { }; + struct FormatterOption { }; + int getWriteError() { return write_error; } void clearWriteError() { setWriteError(0); } @@ -58,36 +101,559 @@ class Print return write((const uint8_t *)buffer, size); } + // default to zero, meaning "a single write may block" // should be overriden by subclasses with buffering virtual int availableForWrite() { return 0; } - size_t print(const __FlashStringHelper *); - size_t print(const String &); - size_t print(const char[]); - size_t print(char); - size_t print(unsigned char, int = DEC); - size_t print(int, int = DEC); - size_t print(unsigned int, int = DEC); - size_t print(long, int = DEC); - size_t print(unsigned long, int = DEC); - size_t print(double, int = 2); - size_t print(const Printable&); - - size_t println(const __FlashStringHelper *); - size_t println(const String &s); - size_t println(const char[]); - size_t println(char); - size_t println(unsigned char, int = DEC); - size_t println(int, int = DEC); - size_t println(unsigned int, int = DEC); - size_t println(long, int = DEC); - size_t println(unsigned long, int = DEC); - size_t println(double, int = 2); - size_t println(const Printable&); + size_t println(void); virtual void flush() { /* Empty implementation for backward compatibility */ } + + template _always_inline size_t print(const Ts &...args) { return printMultiple(args...); } + template _always_inline size_t println(const Ts &...args) { size_t t = print(args...); return t + println(); } + + //_always_inline size_t print() { return 0; } + + // Compatibility versions of print that take a second base or + // precision argument that is an integer. + _always_inline size_t print( signed char , int); + _always_inline size_t print( signed short, int); + _always_inline size_t print( signed int , int); + _always_inline size_t print( signed long , int); + _always_inline size_t print(unsigned char , int); + _always_inline size_t print(unsigned short, int); + _always_inline size_t print(unsigned int , int); + _always_inline size_t print(unsigned long , int); + _always_inline size_t print( float , int); + _always_inline size_t print( double , int); + + private: + // Base case for the argument recursion - no more things to print + _always_inline size_t printMultiple() { return 0; } + + // Simplest case: Just a value, no formatters or options (used when + // none of the below overloads match): Look up the default formatter + // for this value, and add it to the argument list. + template + _always_inline size_t printMultiple(const T &arg, const Ts &...args) { + // This might lead to infinite template instantion + //return print(arg, DefaultFormatter<>(), args...); + return printMultiple(arg, DefaultFormatterFor(arg), args...); + } + + // A value and a formatter is specified without any options (or the + // below overloads have applied the options): Let the formatter + // print the value. + template< + typename T, + typename TFormatter, + typename ...Ts, + detail::enable_if_base_of* = nullptr + > + _always_inline size_t printMultiple(const T &arg, const TFormatter &formatter, const Ts &...args) { + size_t n = formatter.printTo(this, arg); + return n + printMultiple(args...); + } + + // A value, a formatter and an option is specified: Apply the option + // to the formatter. + template< + typename T, + typename TFormatter, + typename TOption, + typename ...Ts, + detail::enable_if_base_of* = nullptr, + detail::enable_if_base_of* = nullptr + > + _always_inline size_t printMultiple(const T &arg, const TFormatter &formatter, const TOption &option, const Ts &...args) { + return printMultiple(arg, applyOption(formatter, option), args...); + } + + // A value and an option is specified: Look up the default formatter + // for this value and option and add it to the argument list. + template< + typename T, + typename TOption, + typename ...Ts, + detail::enable_if_base_of* = nullptr + > + _always_inline size_t printMultiple(const T &arg, const TOption &option, const Ts &...args) { + auto formatter = DefaultFormatterFor(arg, option); + return printMultiple(arg, formatter, option, args...); + } + + // The next two overloads unpack an OptionList when it is + // supplied. These are not strictly needed (OptionList implements + // operator+ to apply each of its arguments to the formatter in + // turn), but without these the error messages are less clear if + // incompatible options are mixed (with the below overloads the + // error shows the formatter and the incompatible option, while + // without them only the formatter and the entire option list are + // shown. + // + // If we keep these, OptionList::addToFormatter and the related + // operator+ overload can be removed. + // + // If we add one more overload for (Value, OptionList, ...), we can + // also remove the DefaultFormatterFor definition for OptionList. + template< + typename T, + typename TFormatter, + typename THead, + typename TTail, + typename ...Ts, + detail::enable_if_base_of* = nullptr + > + _always_inline size_t printMultiple(const T &arg, const TFormatter &formatter, const Formatters::OptionList &list, const Ts &...args) { + return printMultiple(arg, formatter, list.head, list.tail, args...); + } + + // Base case for the end of the OptionList + template< + typename T, + typename TFormatter, + typename THead, + typename ...Ts, + detail::enable_if_base_of* = nullptr + > + _always_inline size_t printMultiple(const T &arg, const TFormatter &formatter, const Formatters::OptionList &list, const Ts &...args) { + return printMultiple(arg, formatter, list.head, args...); + } }; + +void accepts_formatter(const Print::Formatter*); +void accepts_option(const Print::FormatterOption*); + +// Global function to apply an option on a given formatter. This version +// just calls the applyOption method on the formatter, if it exists. By +// going through this global function, it is possible to add overloads +// to add options for an existing formatter. +template +_always_inline inline auto applyOption(const TFormatter& formatter, const TOption& option) +-> decltype(formatter.applyOption(option)) { + return formatter.applyOption(option); +} + +// Namespace for more advanced custom formatter stuff, to prevent +// cluttering the global namespace. Could be removed if needed. +namespace Formatters { + +class DefaultFormatter : public Print::Formatter { + public: + // Common base class for our options + struct FormatterOption : Print::FormatterOption { }; + + // TODO: Defining option types and functions is a bit verbose, it + // would be nice if there was less boilerplate. Also, it would be + // nice if these types would be hidden and only a constructor + // function would be visible. + // To reduce verbosity: + // - Define a template superclass that allocates storage for a + // value with a templated type. Downside is that inheriting + // constructors is very verbose (needs repeating template + // parameters). + // - Define a template helper class that allocates storage, but also + // has a tag template parameter to make it unique (so you do not + // need to subclass, but can use a type alias to it instead). + // This is still a bit verbose because you need to define a tag + // and the alias. + /* + template + struct FormatterOptionHelper : public FormatterOption { + TValue value; + constexpr FormatterOptionHelper(TValue value) : value(value) { } + }; + + struct FormatOptionPrecisionTag; + using FormatOptionPrecision = FormatterOptionHelper; + */ + // Both options might be slightly less verbose, but also more + // complex, so might not be worth the trouble. + // + // To reduce visibility: + // - Make the option classes private, and make the global + // builder functions friend. Downside is an extra friend + // declaration. + // It seems that using a friend *definition* (instead of a + // declaration) is appropriate, which defines a global function + // and makes it a friend at the same time. However, it seems this + // creates a hidden function, only accessible through ADL, which + // does not apply to an argumentless function. + // A normal friend declaration can also be problematic, since it + // forwards declares the function in the same namespace as the + // enclosing class. If that is not where it should end up, the + // builder function should be forward declared before the + // formatter class. But since it references the formatter class, + // this gets complex with forward declarations. This might be ok + // if the formatter class is in the global namespace. + // - Make the option classes private, add a public static builder + // method and replace the global builder function by a constexpr + // function pointer. Downside is an extra variable, which *might* + // end up in the final binary. + + struct FormatOptionBase : FormatterOption { + constexpr FormatOptionBase(uint8_t value) : value(value) { } + uint8_t value; + }; + constexpr DefaultFormatter applyOption(FormatOptionBase o) { + return {o.value, this->min_width, this->precision}; + } + + struct FormatOptionPrecision : FormatterOption { + constexpr FormatOptionPrecision(uint8_t value) : value(value) { } + uint8_t value; + }; + constexpr DefaultFormatter applyOption(FormatOptionPrecision o) { + return {this->base, this->min_width, o.value}; + } + + struct FormatOptionMinWidth : FormatterOption { + constexpr FormatOptionMinWidth(uint8_t value) : value(value) { } + uint8_t value; + }; + constexpr DefaultFormatter applyOption(FormatOptionMinWidth o) { + return {this->base, o.value, this->precision}; + } + + constexpr DefaultFormatter(uint8_t base = 10, uint8_t min_width = 0, uint8_t precision = 2) + : base(base), min_width(min_width), precision(precision) + { } + + //static constexpr FormatOptionPrecision FORMAT_PRECISION(uint8_t prec) { return {prec}; } + + /* String printing, defined in cpp file */ + size_t printTo(Print*, const __FlashStringHelper *) const; + size_t printTo(Print*, const String &) const; + + /* Stuff that is easy to print */ + _always_inline size_t printTo(Print* p, const char str[]) const { return p->write(str); } + _always_inline size_t printTo(Print* p, const char c ) const { return p->write(c); } + _always_inline size_t printTo(Print* p, const Printable &x ) const { return x.printTo(*p); } + + /* Integer printing, upcast to (unsigned) long and then printed using + * a shared print(Signed)Number function. */ + _always_inline size_t printTo(Print* p, signed char n) const { return printSignedNumber(p, n); } + _always_inline size_t printTo(Print* p, signed short n) const { return printSignedNumber(p, n); } + _always_inline size_t printTo(Print* p, signed int n) const { return printSignedNumber(p, n); } + _always_inline size_t printTo(Print* p, signed long n) const { return printSignedNumber(p, n); } + _always_inline size_t printTo(Print* p, unsigned char n) const { return printNumber(p, n); } + _always_inline size_t printTo(Print* p, unsigned short n) const { return printNumber(p, n); } + _always_inline size_t printTo(Print* p, unsigned int n) const { return printNumber(p, n); } + _always_inline size_t printTo(Print* p, unsigned long n) const { return printNumber(p, n); } + + /* Float is converted to double first */ + _always_inline size_t printTo(Print* p, float n) const { return printFloat(p, n); } + _always_inline size_t printTo(Print* p, double n) const { return printFloat(p, n); } + + private: + size_t printNumber(Print*, unsigned long) const; + size_t printSignedNumber(Print*, signed long) const; + size_t printFloat(Print*, double) const; + + uint8_t base; + uint8_t min_width; + uint8_t precision; + +}; + +/****************************************************************** + * OptionList */ + +template +class OptionList : public Print::FormatterOption { + public: + THead head; + TTail tail; + + constexpr OptionList(const THead& head, const TTail& tail) : head(head), tail(tail) { } + + // Apply our contained options to a formatter + template< + typename TFormatter, + detail::enable_if_base_of* = nullptr + > + auto addToFormatter(const TFormatter& formatter) const + -> decltype(formatter + this->head + this->tail) + { + return formatter + this->head + this->tail; + } + + // Make all OptionLists friends of us + template friend class OptionList; + + public: + // Append another option + template< + typename TOption, + detail::enable_if_base_of* = nullptr + > + constexpr auto operator+(const TOption& option) const + -> OptionListtail + option)> + { + return {this->head, this->tail + option}; + } +}; + +template +struct OptionList : public Print::FormatterOption { + public: + THead head; + + constexpr OptionList(const THead& head) : head(head) { } + + // Apply our contained options to a formatter + template< + typename TFormatter, + detail::enable_if_base_of* = nullptr + > + constexpr auto addToFormatter(const TFormatter& formatter) const + -> decltype(formatter + this->head) + { + return formatter + this->head; + } + + // Make all OptionLists friends of us + template friend class OptionList; + + public: + // Append another option + template< + typename TOption, + detail::enable_if_base_of* = nullptr + > + constexpr auto operator+(const TOption& option) const + -> OptionList> + { + return {this->head, option}; + } + +}; + +// Apply an option list by adding it to a formatter (e.g. by passing it +// to print()) +template +constexpr auto applyOption(const TFormatter& formatter, const OptionList& list) +-> decltype(list.addToFormatter(formatter)) +{ + return list.addToFormatter(formatter); +} + +// Start an OptionList by adding two options +template< + typename TOption1, + typename TOption2, + detail::enable_if_base_of* = nullptr, + detail::enable_if_base_of* = nullptr +> +constexpr auto operator+(const TOption1& first, const TOption2& second) +-> OptionList> +{ + return {first, second}; +} + +// The DefaultFormatter for an option list is the DefaultFormatter for +// the first option +template +auto DefaultFormatterFor(TValue value, OptionList list) +-> decltype(DefaultFormatterFor(value, list.head)) { + return DefaultFormatterFor(value, list.head); +} + +/* End of OptionList stuff * + ******************************************************************/ + +} // namespace Formatters + +// The DefaultFormatterFor function is used to define what formatter to +// use for a value when no formatter is explicitly specified. This is +// primarily intended to pick a formatter based on the value type (first +// argument), but it is also possible to look at the value itself when +// building the formatter. Note that it is not possible to return a +// different *type* of formatter based on the value, it is possible to +// set options in the formatter based on the value. +// TODO: Document ADL stuff? +// TODO: Should this use a reference argument? Using a value argument +// requires a copy constructor (even when a value *can* be printed +// through an implicit conversion to another printable type). Can we +// somehow apply this implicit conversion for DefaultFormatterFor too? +// Otherwise you'd get the formatter for the original type, whereas +// there might be a different formatter for the converted type? +template +inline Formatters::DefaultFormatter DefaultFormatterFor(T) { + return {}; +} + +// Overload that decides on a formatter to use based on the value to +// print as well as the (first) formatter option passed. See +// `DefaultFormatterFor(T)` overload for more details. +template +inline Formatters::DefaultFormatter DefaultFormatterFor(T, Formatters::DefaultFormatter::FormatterOption) { + return {}; +} + +// TODO: Fix this in Arduino.h +#undef HEX + +// TODO: Finalize options +inline constexpr Formatters::DefaultFormatter::FormatOptionMinWidth FORMAT_MIN_WIDTH(uint8_t min_width) { return {min_width}; } +inline constexpr Formatters::DefaultFormatter::FormatOptionBase FORMAT_BASE(uint8_t base) { return {base}; } +inline constexpr Formatters::DefaultFormatter::FormatOptionPrecision FORMAT_PRECISION(uint8_t prec) { return {prec}; } +constexpr Formatters::DefaultFormatter::FormatOptionBase HEX = FORMAT_BASE(16); +//constexpr auto FORMAT_PRECISION = Formatters::DefaultFormatter::FORMAT_PRECISION; + +// Compatibility versions of print that take a second base or +// precision argument that is an integer. Defined here, since they need +// to refer to DefaultFormatter options. +inline size_t Print::print( signed char n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print( signed short n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print( signed int n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print( signed long n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print(unsigned char n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print(unsigned short n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print(unsigned int n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print(unsigned long n, int base) { return print(n, FORMAT_BASE(base)); } +inline size_t Print::print( float n, int prec) { return print(n, FORMAT_PRECISION(prec)); } +inline size_t Print::print( double n, int prec) { return print(n, FORMAT_PRECISION(prec)); } + +#undef _always_inline + #endif + +// Idea: Raise errors when DefaultFormatter is used with int-only +// options (e.g. integer base), but prints a string or otherwise +// incompatible type. This can be done by extending DefaultFormatter +// with a bitmask or bool template argument(s) that track what options +// have been set, so they can be static_asserted against in specific +// printTo versions. This does require that *all* methods in +// DefaultFormatter are always_inline, to prevent duplicates. This means +// the actual printing code must again live elsewhere, which might not +// be ideal. Also, error messages are then complicated with these +// template arguments. This can be solved by doing just the checks and +// forwarding *all* printTo calls to another class (including a +// catch-all template), so that when you try printing any unsupported +// types you still get the proper "no such method to call, candidates +// are..." error message. +// +// Idea: Instead of using a Formatter and FormatterOption superclass, +// create them as wrapper classes, so you can match them directly. Then +// unpack and repack the values inside whenever you work with them. This +// avoids the need for these somewhat clunky checks using +// enable_if_base_of. Declaring an option would then be a bit more +// involved and less intuitive, though. Note that we cannot just declare +// the parameters to be of the superclass type, since we need to know +// the actual formatter/option class too. +// +// Idea: Because DefaultFormatter::printTo accesses options through +// this, the compiler seems to force the options onto the stack and +// passes a pointer. It would probably be more efficient to just load +// the options directly into registers instead. To do this, +// all DefaultFormatter::printTo could be changed to static methods and +// accept the DefaultFormatter instance by-value, and a generic +// non-static printTo could be added that forwards all calls to the +// static versions. Doing this in DefaultFormatter instead of coding +// this in Print, leaves control over by-value/by-ref at the +// formatter author and probably gives the cleanest Formatter interface +// (an alternative would to just have Print call a static printTo, +// in which case the formatter could still decide to accept the +// formatter instance by reference or by value). +// +// Limitation: Default formatters for a value type and/or option type +// are specified using overloads of the DefaultFormatterFor function. +// You can use templates to get wildcard overloads (e.g. specify a +// default formatter for all options passed to a specific type, or a +// default formatter for any type combined with a specific formatter). +// +// If both overloads exist, this might cause ambiguity. e.g. when you +// have a (FooT value, *) overload and a (*, BarOption) overload, doing +// print(FooT(), BarOption()) is ambiguous. Usually, this will also be +// invalid (the formatter belonging to BarOption will likely not know +// how to print a FooT anyway). There might be cases where it is not, +// but it is not clear which of the two to favor in this case. If +// needed, some kind of priority tag argument could later be added, with +// a fallback to the regular tag-less version). Also, even in the +// invalid case, the "ambiguous" error message is not so nice. +// +// Note that the current approach of using a formatter-specific +// superclass (e.g. DefaultFormatter::FormatterOption) between the +// actual option class and Print::FormatterOption already makes +// overloads that use it (and thus need parent class conversion) less +// specific than templated overloads (which match their arguments +// exactly). This resolves the ambiguity, but I'm not sure if this is +// the right resolution. + +// Limitation: DefaultFormatterFor relies on argument-dependent-lookup +// (ADL) to work properly. Normally, when a function is called, only +// overloads defined up to where the function call is defined are +// considered (e.g. the call the DefaultFormatterFor in Print.h). In +// this case, we want to allow defining more overloads later. We can +// make this work because DefaultFormatterFor is only called from +// template functions, and for those ADL happens at template +// *instantiation time*, rather than *definition time*. +// +// ADL happens for all types in a namespace (including the root +// namespace). Notably, this means ADL happens for class types (options +// and custom value types), but not for native types (e.g. int does not +// live in a namespace). In practice, this means that replacing the +// default formatter for a native type (without any options) is not +// possible, since the reference to e.g. DefaultFormatterFor(int) is +// looked up in the set of overloads that were defined at template +// definition time, not at instantiation time. +// +// Two possible workarounds for this would be to add a wrapper class +// around values before passing them to DefaultFormatterFor, or adding +// an unused dummy argument that forces ADL. The latter is probably +// easiest, and if the dummy argument is called NoOption and is the +// second argument, that might actually be fairly easy to work with as +// well. +// +// +// Mail +// +// For adding options to formatters, I'm using the overloaded addition +// operator. This has the advantage that you can provide it inside the +// formatter class, but also outside of it. This means you can +// potentially define more formatter options, and apply them to an +// existing formatter, without having to modify the formatter. If we +// prefer a normal method/function, the same could probably be obtained +// using a global overloaded addOption function (with a template version +// that defers to an addOption method if it exists). After writing this, +// I realized that a method/function name is probably gives more +// readable errors (e.g. "no such function applyOption(DefaultFormatter, +// SomeOption)", rather than "no such operator +// operator+(DefaultFormatter, SomeOption)"), so we should probably do +// that. +// TODO: This was changed to applyOption, reword +// +// Formatters and options are currently passed around as const +// references, meaning that their printTo methods and addition operators +// must be const methods. We can probably also provide non-const +// reference and/or rvalue-reference versions of the variadic print +// methods (probably even automatically template-generated by using && +// and std::forward), in case we want to allow implementing formatters +// than modify themselves rather than return a new formatter (though I +// can't really come up with a usecase for this - maybe a formatter that +// counts characters written for alignment?). +// +// Compatibility with C++ <= 11 +// +// ADL needed for DefaultFormatterFor - no primitive types +// +// Compare with Printable +// +// Customization: Custom formatter (with or without options), +// optionlist, custom option for existing formatter using applyOption +// overload. +// +// Error messages (a bit fuzzy due to "when instantiating" clauses, but +// the actual error is usually ok. A lot of gunk follows, though). +// +// TODO: Use NoOption dummy argument to DefaultFormatterFor to force +// ADL? +// +// TODO: Naming of option classes (shows up in error message) +// +// TODO: Getters and setters for DefaultFormatter options? Helps with +// extending. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy