@@ -1735,10 +1735,22 @@ FormatSpec!Char singleSpec(Char)(Char[] fmt)
17351735 * `toString` should have one of the following signatures:
17361736 *
17371737 * ---
1738- * const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt);
1739- * const void toString(scope void delegate(const(char)[]) sink, string fmt);
1740- * const void toString(scope void delegate(const(char)[]) sink);
1741- * const string toString();
1738+ * void toString(W)(ref W w, const ref FormatSpec fmt)
1739+ * void toString(W)(ref W w)
1740+ * string toString();
1741+ * ---
1742+ *
1743+ * Where `W` is an $(REF_ALTTEXT output range, isOutputRange, std,_range,primitives)
1744+ * which accepts characters. The template type does not have to be called `W`.
1745+ *
1746+ * The following overloads are also accepted for legacy reasons or for use in virtual
1747+ * functions. It's recommended that any new code forgo these overloads if possible for
1748+ * speed and attribute acceptance reasons.
1749+ *
1750+ * ---
1751+ * void toString(scope void delegate(const(char)[]) sink, const ref FormatSpec fmt);
1752+ * void toString(scope void delegate(const(char)[]) sink, string fmt);
1753+ * void toString(scope void delegate(const(char)[]) sink);
17421754 * ---
17431755 *
17441756 * For the class objects which have input range interface,
@@ -1917,13 +1929,53 @@ void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref F
19171929}
19181930
19191931/**
1920- * Formatting of a `struct` with a defined `toString`.
1932+ * Formatting a struct by defining a method `toString`, which takes an output
1933+ * range.
1934+ *
1935+ * It's recommended that any `toString` using $(REF_ALTTEXT output _rangew, isOutputRange, std,_range,primitives)
1936+ * use $(REF put, std,_range,primitives) rather than use the `put` method of the range
1937+ * directly.
1938+ */
1939+ @safe unittest
1940+ {
1941+ import std.array : appender;
1942+
1943+ static struct Point
1944+ {
1945+ int x, y;
1946+
1947+ void toString (W)(ref W writer, const ref FormatSpec! char f) if (isOutputRange! (W, char ))
1948+ {
1949+ // make sure to use std.range.primitives.put and not writer.put directly
1950+ // see put's docs for more details
1951+ put(writer, " (" );
1952+ formatValue(writer, x, f);
1953+ put(writer, " ," );
1954+ formatValue(writer, y, f);
1955+ put(writer, " )" );
1956+ }
1957+ }
1958+
1959+ auto w = appender! string ();
1960+ auto spec = singleSpec(" %s" );
1961+ auto p = Point(16 , 11 );
1962+
1963+ formatValue(w, p, spec);
1964+ assert (w.data == " (16,11)" );
1965+ }
1966+
1967+ /**
1968+ * Another example of formatting a `struct` with a defined `toString`,
1969+ * this time using the `scope delegate` method.
19211970 *
1922- * `formatValue` also allows to reuse existing format specifiers:
1971+ * This method is now discouraged. Please use the output range method
1972+ * instead.
1973+ *
1974+ * `formatValue` also allows to reuse existing format specifiers:
19231975 */
19241976@safe unittest
19251977{
1926- struct Point
1978+ static struct Point
19271979 {
19281980 int x, y;
19291981
@@ -3455,13 +3507,30 @@ if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
34553507 formatTest(e2, " [A, B, C]" );
34563508}
34573509
3458- template hasToString (T, Char)
3510+ private template hasToString (T, Char)
34593511{
34603512 static if (isPointer! T && ! isAggregateType! T)
34613513 {
34623514 // X* does not have toString, even if X is aggregate type has toString.
34633515 enum hasToString = 0 ;
34643516 }
3517+ else static if (is (typeof (
3518+ {T val = void ; FormatSpec! Char f; static struct S {void put(Char s){}}
3519+ S s; val.toString(s, f);
3520+ // force toString to take output range by ref
3521+ static assert (ParameterStorageClassTuple! (T.toString! (S))[0 ] == ParameterStorageClass.ref_);}
3522+ )))
3523+ {
3524+ enum hasToString = 6 ;
3525+ }
3526+ else static if (is (typeof (
3527+ {T val = void ; static struct S {void put(Char s){}}
3528+ S s; val.toString(s);
3529+ static assert (ParameterStorageClassTuple! (T.toString! (S))[0 ] == ParameterStorageClass.ref_);}
3530+ )))
3531+ {
3532+ enum hasToString = 5 ;
3533+ }
34653534 else static if (is (typeof ({ T val = void ; FormatSpec! Char f; val.toString((const (char )[] s){}, f); })))
34663535 {
34673536 enum hasToString = 4 ;
@@ -3484,11 +3553,59 @@ template hasToString(T, Char)
34843553 }
34853554}
34863555
3556+ @safe unittest
3557+ {
3558+ static struct A
3559+ {
3560+ void toString (Writer )(ref Writer w) if (isOutputRange! (Writer , string )) {}
3561+ }
3562+ static struct B
3563+ {
3564+ void toString (scope void delegate (const (char )[]) sink, FormatSpec! char fmt) {}
3565+ }
3566+ static struct C
3567+ {
3568+ void toString (scope void delegate (const (char )[]) sink, string fmt) {}
3569+ }
3570+ static struct D
3571+ {
3572+ void toString (scope void delegate (const (char )[]) sink) {}
3573+ }
3574+ static struct E
3575+ {
3576+ string toString () {return " " ;}
3577+ }
3578+ static struct F
3579+ {
3580+ void toString (Writer )(ref Writer w, const ref FormatSpec! char fmt) if (isOutputRange! (Writer , string )) {}
3581+ }
3582+
3583+ static assert (hasToString! (A, char ) == 5 );
3584+ static assert (hasToString! (B, char ) == 4 );
3585+ static assert (hasToString! (C, char ) == 3 );
3586+ static assert (hasToString! (D, char ) == 2 );
3587+ static assert (hasToString! (E, char ) == 1 );
3588+ static assert (hasToString! (F, char ) == 6 );
3589+ }
3590+
34873591// object formatting with toString
34883592private void formatObject (Writer , T, Char)(ref Writer w, ref T val, const ref FormatSpec! Char f)
34893593if (hasToString! (T, Char))
34903594{
3491- static if (is (typeof (val.toString((const (char )[] s){}, f))))
3595+ enum overload = hasToString! (T, Char);
3596+
3597+ static if (overload == 6 )
3598+ {
3599+ val.toString(w, f);
3600+ }
3601+ else static if (overload == 5 )
3602+ {
3603+ val.toString(w);
3604+ }
3605+ // not using the overload enum to not break badly defined toString overloads
3606+ // e.g. defining the FormatSpec as ref and not const ref led this function
3607+ // to ignore that toString overload
3608+ else static if (is (typeof (val.toString((const (char )[] s){}, f))))
34923609 {
34933610 val.toString((const (char )[] s) { put(w, s); }, f);
34943611 }
@@ -3505,12 +3622,14 @@ if (hasToString!(T, Char))
35053622 put(w, val.toString());
35063623 }
35073624 else
3625+ {
35083626 static assert (0 );
3627+ }
35093628}
35103629
35113630void enforceValidFormatSpec (T, Char)(const ref FormatSpec! Char f)
35123631{
3513- static if (! isInputRange! T && hasToString! (T, Char) != 4 )
3632+ static if (! isInputRange! T && hasToString! (T, Char) < 4 )
35143633 {
35153634 enforceFmt(f.spec == ' s' ,
35163635 " Expected '%s' format specifier for type '" ~ T.stringof ~ " '" );
0 commit comments