I just slapped this together in the other thread, let me copy/paste it here, talk about it a bit, then I'll finish reading what you wrote:

struct GCSink(T) {
    // so this is a reference type
    private struct Impl {
        T[] data;
        void put(T t) { data ~= t; }
        T[] finish() { return data; }
    }
    Impl* impl;
    alias impl this;
    void start() {
        impl = new Impl;
    }
}

struct StaticSink(T) {
    T[] container;
    this(T[] c) { container = c; }
    size_t size;
    void start() { size = 0; }
    void put(T t) { container[size++] = t; }
    T[] finish() { return container[0 .. size]; }
}
StaticSink!T staticSink(T)(T[] t) {
    return StaticSink!T(t);
}

T[] toUpper(T, OR = GCSink!T)(in T[] data, OR output = OR()) {
    output.start();
    foreach(d; data)
       output.put(d & ~0x20);
    return output.finish();
}

void main() {
    import std.stdio;
    writeln(toUpper("cool")); // default: GC
    char[10] buffer;
auto received = toUpper("cool", staticSink(buffer[])); // custom static sink
    assert(buffer.ptr is received.ptr);
    assert(received == "COOL");
}

====

In addition to put(), I also added start() and finish(). There's precedent for this in Phobos already: the std.digest output ranges have methods like this. Like std.digest, put builds it up, then finish returns the final product.

These wouldn't be required functions on all ranges. Finish might even return null or void, if it was a sink into a write function or something. It could also close a file or something like that.

But, if it does build into an array, finish ought to be defined to return an analogous input range (or slice) into the data it just built for easier chaining and basic simple usage.

(Appender in phobos has a kinda similar thing called data, but I think this breaks things since it might be called at any time. Finish, on the other hand, could be defined that any puts after it are invalid.

start is used to restart things. Calling start at any time should reset the range to reuse the buffer.


I used the Impl* on the GC one so the default worked. Ref with a default argument would fail because it isn't an lvalue, so that would kill the simplicity.


I called staticSink on the buffer to build the range... which my first post said I wanted to avoid but I kind like it this way better. It isn't a hassle to call and keeps a nice separation between the view and the container.

Reply via email to