Use case: eliminate hidden allocations in buildPath

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Dec 4 16:38:54 PST 2013


On Wed, Dec 04, 2013 at 03:14:48PM -0800, Andrei Alexandrescu wrote:
> Hello,
> 
> 
> Walter and I were talking about eliminating the surreptitious
> allocations in buildPath:
> 
> http://dlang.org/phobos/std_path.html#.buildPath
> 
> We'd need to keep the existing version working, so we're looking at
> adding one or more new overloads. We're looking at giving the user
> the option to control any needed memory allocation (or even arrange
> things such that there's no memory allocated at all).
> 
> It's a generous design space, so although we have a couple of ideas
> let's hear others first.
[...]

What about a new overload that takes an output range instead of
returning a string? The caller can then create the appropriate output
range that does whatever is desired (malloc a buffer, use a static
fixed-size buffer, etc.), and buildPath doesn't have to care about the
implementation details. So, something like:

	void buildPath(OutputRange,Range)(OutputRange output,
					  Range segments)
		if (isOutputRange!(OutputRange, ElementType!Range))
	{ ... }

//

On a related note, I wonder if it's profitable to extend the concept of
an output range to include void delegates that take range elements as
arguments. The current toString overload looked for by std.format, for
example, has this signature:

	void toString(scope void delegate(const(char)[] s) dg);

The delegate here essentially behaves like an output range (it takes
snippets of string data and presumably appends them to some buffer
somewhere). So why not extend the concept of output ranges to include
such delegates, so that we can write:

	void toString(R)(R r) if (isOutputRange!R);

Then any output range can be used as the target of a string conversion,
e.g., writing straight to a file or network socket without the
unnecessary buffering that returning a string implies.

"But!" I hear you cry, "you can't call a delegate as dg.put(...), which
you have to if it is to conform to the output range interface!" To that
I'd say:

	module std.range;
	...
	void put(R,T)(R dg, T data)
		if (is(R == void delegate(T)) ||
		    is(R == void function(T)))
	{
		dg(data);
	}

If I'm not mistaken, this should make isOutputRange true for functions
and delegates that take the appropriate argument types.

This then allows us to use buildPath with no hidden allocations, for
example:

	char[1024] buffer;

	void appendToBuf(const(char)[] data) { ... }

	void main() {
		buildPath(&appendToBuf, "usr", "local", "share", "filename");
	}


T

-- 
Three out of two people have difficulties with fractions. -- Dirk Eddelbuettel


More information about the Digitalmars-d mailing list