Skip to content

Commit 3a8f31a

Browse files
Fix #9811 Remove const from Tuple.toString (#10646)
Now, member structs won't become const due to transitiveness and the code in `std.format` can choose (erroneously) non-cost toString() functions of member structs. An expert has to decide whether this is worth the breaking change. Now, code that uses `const` tuples and calls toString directly on them will break. Alternatively, we could use [Template this parameters](https://dlang.org/spec/template.html#template_this_parameter). That way, we get a template parameter with the real type of this. Then we can cast away constness of `this` when we now for certain that it isn't, since `is(T != typeof(this))` (where T is the template this parameter). Of course, this implies making toString `@trusted` too. This might also lead to unforeseen bugs when `const` is cast away but the member objects are actually const. I'm not sure how this works. Fwiw, currently std.format and `std.conv: to` already intercepts const tuples, thus it at least won't call toString directly. The breaking change will only effect code that calls toString on const tuples directly. That's why I have added non-prescritive tests. If something in std.format changes, they'll be alerted and can then decide whether to change the tests or whether this module also needs work, in case this would lead a bigger breaking change. Followup to #10645.
1 parent 310bd9b commit 3a8f31a

File tree

1 file changed

+38
-4
lines changed

1 file changed

+38
-4
lines changed

std/typecons.d

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,11 +1294,11 @@ if (distinctFieldNames!(Specs))
12941294
* Returns:
12951295
* The string representation of this `Tuple`.
12961296
*/
1297-
string toString()() const
1297+
string toString()()
12981298
{
12991299
import std.array : appender;
13001300
auto app = appender!string();
1301-
this.toString((const(char)[] chunk) => app ~= chunk);
1301+
toString((const(char)[] chunk) => app ~= chunk);
13021302
return app.data;
13031303
}
13041304

@@ -1320,14 +1320,14 @@ if (distinctFieldNames!(Specs))
13201320
* sink = A `char` accepting delegate
13211321
* fmt = A $(REF FormatSpec, std,format)
13221322
*/
1323-
void toString(DG)(scope DG sink) const
1323+
void toString(DG)(scope DG sink)
13241324
{
13251325
auto f = FormatSpec!char();
13261326
toString(sink, f);
13271327
}
13281328

13291329
/// ditto
1330-
void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) const
1330+
void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt)
13311331
{
13321332
import std.format : format, FormatException;
13331333
import std.format.write : formattedWrite;
@@ -1822,6 +1822,40 @@ private template ReverseTupleSpecs(T...)
18221822
assert(t[0] == 10 && t[1] == "str");
18231823
assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string);
18241824
}
1825+
/* https://github.com/dlang/phobos/issues/9811
1826+
* Note: This is just documenting current behaviour, dependent on `std.format` implementation
1827+
* details. None of this is defined in a spec or should be regarded as rigid.
1828+
*/
1829+
{
1830+
static struct X
1831+
{
1832+
/** Usually, toString() should be const where possible.
1833+
* But as long as the tuple is also non-const, this will work
1834+
*/
1835+
string toString()
1836+
{
1837+
return "toString non-const";
1838+
}
1839+
}
1840+
assert(tuple(X()).to!string == "Tuple!(X)(toString non-const)");
1841+
const t = tuple(X());
1842+
// This is an implementation detail of `format`
1843+
// if the tuple is const, than non-const toString will not be called
1844+
assert(t.to!string == "const(Tuple!(X))(const(X)())");
1845+
1846+
static struct X2
1847+
{
1848+
string toString() const /* const toString will work in more cases */
1849+
{
1850+
return "toString const";
1851+
}
1852+
}
1853+
assert(tuple(X2()).to!string == "Tuple!(X2)(toString const)");
1854+
const t2 = tuple(X2());
1855+
// This is an implementation detail of `format`
1856+
// if the tuple is const, than non-const toString will not be called
1857+
assert(t2.to!string == "const(Tuple!(X2))(toString const)");
1858+
}
18251859
{
18261860
Tuple!(int, "a", double, "b") x;
18271861
static assert(x.a.offsetof == x[0].offsetof);

0 commit comments

Comments
 (0)