diff --git a/std/conv.d b/std/conv.d index 258c0266d..a10f4da7f 100644 --- a/std/conv.d +++ b/std/conv.d @@ -50,7 +50,7 @@ module std.conv; public import std.ascii : LetterCase; import std.meta; -import std.range.primitives; +import std.range; import std.traits; import std.typecons : Flag, Yes, No, tuple, isTuple; @@ -1996,7 +1996,7 @@ if (isInputRange!S && isSomeChar!(ElementEncodingType!S) && /// ditto private T toImpl(T, S)(S value, uint radix) -if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S) && +if (isSomeFiniteCharInputRange!S && isIntegral!T && is(typeof(parse!T(value, radix)))) { scope(success) diff --git a/std/file.d b/std/file.d index 315e054cb..927f06eb5 100644 --- a/std/file.d +++ b/std/file.d @@ -89,7 +89,7 @@ import std.datetime.date : DateTime; import std.datetime.systime : Clock, SysTime, unixTimeToStdTime; import std.internal.cstring; import std.meta; -import std.range.primitives; +import std.range; import std.traits; import std.typecons; @@ -313,8 +313,7 @@ Throws: $(LREF FileException) on error. */ void[] read(R)(R name, size_t upTo = size_t.max) -if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) return readImpl(name, name.tempCString!FSChar(), upTo); @@ -500,7 +499,7 @@ version (linux) @safe unittest $(REF UTFException, std, utf) on UTF decoding error. +/ S readText(S = string, R)(auto ref R name) -if (isSomeString!S && (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || is(StringTypeOf!R))) +if (isSomeString!S && (isSomeFiniteCharInputRange!R || is(StringTypeOf!R))) { import std.algorithm.searching : startsWith; import std.encoding : getBOM, BOM; @@ -736,8 +735,7 @@ Throws: $(LREF FileException) on error. See_also: $(REF toFile, std,stdio) */ void write(R)(R name, const void[] buffer) -if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) && - !isConvertibleToString!R) +if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R) { static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) writeImpl(name, name.tempCString!FSChar(), buffer, false); @@ -785,8 +783,7 @@ Params: Throws: $(LREF FileException) on error. */ void append(R)(R name, const void[] buffer) -if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) && - !isConvertibleToString!R) +if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R) { static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) writeImpl(name, name.tempCString!FSChar(), buffer, true); @@ -915,10 +912,8 @@ version (Windows) private void writeImpl(scope const(char)[] name, scope const(F * Throws: $(LREF FileException) on error. */ void rename(RF, RT)(RF from, RT to) -if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF) - && !isConvertibleToString!RF && - (isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) || isSomeString!RT) - && !isConvertibleToString!RT) +if ((isSomeFiniteCharInputRange!RF || isSomeString!RF) && !isConvertibleToString!RF && + (isSomeFiniteCharInputRange!RT || isSomeString!RT) && !isConvertibleToString!RT) { // Place outside of @trusted block auto fromz = from.tempCString!FSChar(); @@ -1027,8 +1022,7 @@ Params: Throws: $(LREF FileException) on error. */ void remove(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) removeImpl(name, name.tempCString!FSChar()); @@ -1082,7 +1076,7 @@ private void removeImpl(scope const(char)[] name, scope const(FSChar)* namez) @t } version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) +if (isSomeFiniteCharInputRange!R) { auto namez = name.tempCString!FSChar(); @@ -1137,8 +1131,7 @@ Throws: $(LREF FileException) on error (e.g., file not found). */ ulong getSize(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -1233,8 +1226,7 @@ private SysTime statTimeToStdTime(char which)(ref const stat_t statbuf) void getTimes(R)(R name, out SysTime accessTime, out SysTime modificationTime) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -1378,8 +1370,9 @@ version (StdDdoc) out SysTime fileCreationTime, out SysTime fileAccessTime, out SysTime fileModificationTime) - if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R); + if (isSomeFiniteCharInputRange!R || isConvertibleToString!R); + // above line contains both constraints for docs + // (so users know how it can be called) } else version (Windows) { @@ -1387,8 +1380,7 @@ else version (Windows) out SysTime fileCreationTime, out SysTime fileAccessTime, out SysTime fileModificationTime) - if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) + if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { import std.datetime.systime : FILETIMEToSysTime; @@ -1509,8 +1501,7 @@ private void setTimes(R)(R name, SysTime accessTime, SysTime modificationTime) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { auto namez = name.tempCString!FSChar(); static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) @@ -1657,8 +1648,7 @@ private void setTimesImpl(scope const(char)[] names, scope const(FSChar)* namez, $(LREF FileException) if the given file does not exist. +/ SysTime timeLastModified(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -1742,7 +1732,7 @@ else -------------------- +/ SysTime timeLastModified(R)(R name, SysTime returnIfMissing) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) +if (isSomeFiniteCharInputRange!R) { version (Windows) { @@ -1902,8 +1892,7 @@ version (OSX) {} else * true if the file _name specified as input _exists */ bool exists(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { return existsImpl(name.tempCString!FSChar()); } @@ -2004,8 +1993,7 @@ private bool existsImpl(scope const(FSChar)* namez) @trusted nothrow @nogc Throws: $(LREF FileException) on error. +/ uint getAttributes(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -2105,8 +2093,7 @@ if (isConvertibleToString!R) $(LREF FileException) on error. +/ uint getLinkAttributes(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -2214,8 +2201,7 @@ if (isConvertibleToString!R) $(LREF FileException) if the given file does not exist. +/ void setAttributes(R)(R name, uint attributes) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -2323,8 +2309,7 @@ if (isConvertibleToString!R) $(LREF FileException) if the given file does not exist. +/ @property bool isDir(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) { @@ -2503,8 +2488,7 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc $(LREF FileException) if the given file does not exist. +/ @property bool isFile(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) return !name.isDir; @@ -2679,8 +2663,7 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc $(LREF FileException) if the given file does not exist. +/ @property bool isSymlink(R)(R name) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { version (Windows) return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0; @@ -2860,8 +2843,7 @@ Params: Throws: $(LREF FileException) on error. */ void chdir(R)(R pathname) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { // Place outside of @trusted block auto pathz = pathname.tempCString!FSChar(); @@ -2931,8 +2913,7 @@ Throws: if an error occured. */ void mkdir(R)(R pathname) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { // Place outside of @trusted block const pathz = pathname.tempCString!FSChar(); @@ -3135,8 +3116,7 @@ Params: Throws: $(LREF FileException) on error. */ void rmdir(R)(R pathname) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && - !isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) { // Place outside of @trusted block auto pathz = pathname.tempCString!FSChar(); @@ -3202,15 +3182,11 @@ if (isConvertibleToString!R) exists). +/ version (StdDdoc) void symlink(RO, RL)(RO original, RL link) -if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) || - isConvertibleToString!RO) && - (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) || - isConvertibleToString!RL)); +if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) && + (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL)); else version (Posix) void symlink(RO, RL)(RO original, RL link) -if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) || - isConvertibleToString!RO) && - (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) || - isConvertibleToString!RL)) +if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) && + (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL)) { static if (isConvertibleToString!RO || isConvertibleToString!RL) { @@ -3291,11 +3267,9 @@ version (Posix) @safe unittest $(LREF FileException) on error. +/ version (StdDdoc) string readLink(R)(R link) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || - isConvertibleToString!R); +if (isSomeFiniteCharInputRange!R || isConvertibleToString!R); else version (Posix) string readLink(R)(R link) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || - isConvertibleToString!R) +if (isSomeFiniteCharInputRange!R || isConvertibleToString!R) { static if (isConvertibleToString!R) { @@ -4216,8 +4190,8 @@ Params: Throws: $(LREF FileException) on error. */ void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault) -if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF && - isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) && !isConvertibleToString!RT) +if (isSomeFiniteCharInputRange!RF && !isConvertibleToString!RF && + isSomeFiniteCharInputRange!RT && !isConvertibleToString!RT) { // Place outside of @trusted block auto fromz = from.tempCString!FSChar(); @@ -4781,7 +4755,7 @@ private struct DirIteratorImpl } this(R)(R pathname, SpanMode mode, bool followSymlink) - if (isInputRange!R && isSomeChar!(ElementEncodingType!R)) + if (isSomeFiniteCharInputRange!R) { _mode = mode; _followSymlink = followSymlink; diff --git a/std/json.d b/std/json.d index ea22d6357..89def0f02 100644 --- a/std/json.d +++ b/std/json.d @@ -19,7 +19,7 @@ module std.json; import std.array; import std.conv; -import std.range.primitives; +import std.range; import std.traits; /// @@ -929,7 +929,7 @@ Params: options = enable decoding string representations of NaN/Inf as float values */ JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none) -if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) +if (isSomeFiniteCharInputRange!T) { import std.ascii : isDigit, isHexDigit, toUpper, toLower; import std.typecons : Nullable, Yes; @@ -1437,7 +1437,7 @@ Params: options = enable decoding string representations of NaN/Inf as float values */ JSONValue parseJSON(T)(T json, JSONOptions options) -if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) +if (isSomeFiniteCharInputRange!T) { return parseJSON!T(json, -1, options); } diff --git a/std/path.d b/std/path.d index 2d64684b4..20518b863 100644 --- a/std/path.d +++ b/std/path.d @@ -98,7 +98,7 @@ module std.path; import std.file : getcwd; static import std.meta; -import std.range.primitives; +import std.range; import std.traits; version (OSX) @@ -262,8 +262,7 @@ version (Windows) from a path. */ private auto ltrimDirSeparators(R)(R path) -if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || - isNarrowString!R) +if (isSomeFiniteCharInputRange!R || isNarrowString!R) { static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R) { @@ -3213,12 +3212,8 @@ int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b */ int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2) (Range1 filename1, Range2 filename2) -if (isInputRange!Range1 && !isInfinite!Range1 && - isSomeChar!(ElementEncodingType!Range1) && - !isConvertibleToString!Range1 && - isInputRange!Range2 && !isInfinite!Range2 && - isSomeChar!(ElementEncodingType!Range2) && - !isConvertibleToString!Range2) +if (isSomeFiniteCharInputRange!Range1 && !isConvertibleToString!Range1 && + isSomeFiniteCharInputRange!Range2 && !isConvertibleToString!Range2) { alias C1 = Unqual!(ElementEncodingType!Range1); alias C2 = Unqual!(ElementEncodingType!Range2); diff --git a/std/process.d b/std/process.d index 958f606ff..2c68f36b4 100644 --- a/std/process.d +++ b/std/process.d @@ -106,9 +106,8 @@ version (Windows) } import std.internal.cstring; -import std.range.primitives; +import std.range; import std.stdio; -import std.traits : isSomeChar; version (OSX) version = Darwin; @@ -1527,7 +1526,7 @@ package(std) string searchPathFor(scope const(char)[] executable) // current user. version (Posix) private bool isExecutable(R)(R path) @trusted nothrow @nogc -if (isInputRange!R && isSomeChar!(ElementEncodingType!R)) +if (isSomeFiniteCharInputRange!R) { return (access(path.tempCString(), X_OK) == 0); } diff --git a/std/range/package.d b/std/range/package.d index a21f4d017..9f3c71b62 100644 --- a/std/range/package.d +++ b/std/range/package.d @@ -13521,3 +13521,49 @@ pure @safe unittest assert([1, 2, 3, 4].padRight(0, 10)[7 .. 9].equal([0, 0])); } + +/** +This simplifies a commonly used idiom in phobos for accepting any kind of string +parameter. The type `R` can for example be a simple string, chained string using +$(REF chain, std,range), $(REF chainPath, std,path) or any other input range of +characters. + +Only finite length character ranges are allowed with this constraint. + +This template is equivalent to: +--- +isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) +--- + +See_Also: +$(REF isInputRange, std,range,primitives), +$(REF isInfinite, std,range,primitives), +$(LREF isSomeChar), +$(REF ElementEncodingType, std,range,primitives) +*/ +template isSomeFiniteCharInputRange(R) +{ + import std.traits : isSomeChar; + + enum isSomeFiniteCharInputRange = isInputRange!R && !isInfinite!R + && isSomeChar!(ElementEncodingType!R); +} + +/// +@safe unittest +{ + import std.path : chainPath; + import std.range : chain; + + void someLibraryMethod(R)(R argument) + if (isSomeFiniteCharInputRange!R) + { + // implementation detail, would iterate over each character of argument + } + + someLibraryMethod("simple strings work"); + someLibraryMethod(chain("chained", " ", "strings", " ", "work")); + someLibraryMethod(chainPath("chained", "paths", "work")); + // you can also use custom structs implementing a char range +} + diff --git a/std/stdio.d b/std/stdio.d index c5bc4fdfc..bc2d3fe42 100644 --- a/std/stdio.d +++ b/std/stdio.d @@ -50,8 +50,8 @@ import core.stdc.stddef : wchar_t; public import core.stdc.stdio; import std.algorithm.mutation : copy; import std.meta : allSatisfy; -import std.range.primitives : ElementEncodingType, empty, front, - isBidirectionalRange, isInputRange, put; +import std.range : ElementEncodingType, empty, front, isBidirectionalRange, + isInputRange, isSomeFiniteCharInputRange, put; import std.traits : isSomeChar, isSomeString, Unqual, isPointer; import std.typecons : Flag, No, Yes; @@ -555,7 +555,7 @@ Throws: `ErrnoException` if the file could not be opened. /// ditto this(R1, R2)(R1 name) - if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1)) + if (isSomeFiniteCharInputRange!R1) { import std.conv : to; this(name.to!string, "rb"); @@ -563,8 +563,8 @@ Throws: `ErrnoException` if the file could not be opened. /// ditto this(R1, R2)(R1 name, R2 mode) - if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) && - isInputRange!R2 && isSomeChar!(ElementEncodingType!R2)) + if (isSomeFiniteCharInputRange!R1 && + isSomeFiniteCharInputRange!R2) { import std.conv : to; this(name.to!string, mode.to!string); @@ -4642,8 +4642,8 @@ if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && * with appropriately-constructed C-style strings. */ private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r") -if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && - (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) +if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && + (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) { import std.internal.cstring : tempCString; @@ -4684,8 +4684,8 @@ version (Posix) * with appropriately-constructed C-style strings. */ FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc - if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && - (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) + if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && + (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) { import std.internal.cstring : tempCString; diff --git a/std/utf.d b/std/utf.d index a29025a17..5c23684b9 100644 --- a/std/utf.d +++ b/std/utf.d @@ -65,9 +65,9 @@ module std.utf; import std.exception : basicExceptionCtors; import core.exception : UnicodeException; import std.meta : AliasSeq; -import std.range.primitives; -import std.traits : isAutodecodableString, isPointer, isSomeChar, - isSomeString, isStaticArray, Unqual, isConvertibleToString; +import std.range; +import std.traits : isAutodecodableString, isConvertibleToString, isPointer, + isSomeChar, isSomeString, isStaticArray, Unqual; import std.typecons : Flag, Yes, No; @@ -2809,7 +2809,7 @@ if (isSomeChar!C) The number of code units in `input` when encoded to `C` +/ size_t codeLength(C, InputRange)(InputRange input) -if (isInputRange!InputRange && !isInfinite!InputRange && isSomeChar!(ElementType!InputRange)) +if (isSomeFiniteCharInputRange!InputRange) { alias EncType = Unqual!(ElementEncodingType!InputRange); static if (isSomeString!InputRange && is(EncType == C) && is(typeof(input.length))) @@ -2961,7 +2961,7 @@ if (isSomeString!S) * For a lazy, non-allocating version of these functions, see $(LREF byUTF). */ string toUTF8(S)(S s) -if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) +if (isSomeFiniteCharInputRange!S) { return toUTFImpl!string(s); } @@ -3003,7 +3003,7 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) * For a lazy, non-allocating version of these functions, see $(LREF byUTF). */ wstring toUTF16(S)(S s) -if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) +if (isSomeFiniteCharInputRange!S) { return toUTFImpl!wstring(s); } @@ -3047,7 +3047,7 @@ if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) * For a lazy, non-allocating version of these functions, see $(LREF byUTF). */ dstring toUTF32(S)(scope S s) -if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) +if (isSomeFiniteCharInputRange!S) { return toUTFImpl!dstring(s); }