I was working on a project where it dealt with output ranges, but these ranges would ultimately sink into a source that would be inefficient if every single .put(T)
call was made one at a time.
Naturally, I could make a custom OutputRange for just this resource, but I also got the idea that I could make a generalized BufferedOutputRange
that would save individual .put(T)
calls into memory until a threshold is reached, and then make one bulk call to the output stream it wraps with a single .put(T[])
call.
While working on the template for this buffering OutputRange, I originally used ElementType
on the output range I was given, hoping to detect what type of .put(T)
is permitted. However, I found out that ElementType
only works for input ranges as you can see here: https://github.com/dlang/phobos/blob/6bf43144dbe956cfc16c00f0bff7a264fa62408e/std/range/primitives.d#L1265
Trying to find a workaround, I ultimately created this, and my question is, is using such a template a good idea or a terrible idea? Is it safe to assume that ranges should have a put method that may take arrays that I can detect? Should I give up on the idea of detecting the OutputRange type, and instead require the programmer to explicitly declare the output type for fear of them using a range that doesn't take arrays?
Here is the source of what I was thinking of, let me know your thoughts:
import std.range;
import std.traits;
// A specialization of std.range.ElementType which also considers output ranges.
template ElementType(R)
if (is(typeof(R.put) == function)) // Avoid conflicts with std.range.ElementType.
{
// Static foreach generates code, it is not a true loop.
static foreach (t; __traits(getOverloads, R, "put")) {
pragma(msg, "Found put method, params=", Parameters!(t).length);
// Because all code gets generated, we use a 'done' alias to
// tell us when to stop.
static if (!is(done)) {
// Attempts to save Parameters!(t) into a variable fail, so it is repeated.
static if (Parameters!(t).length == 1 && is(Parameters!(t)[0] T : T[])) {
pragma(msg, "put for array found");
// Setting the name of the template replaces calls
// to ElementType!(...) with T.
alias ElementType = T;
alias done = bool;
} else static if (Parameters!(t).length == 1 && is(Parameters!(t)[0] T)) {
pragma(msg, "put for single found");
alias ElementType = T;
alias done = bool;
}
}
}
static if (!is(done)) {
alias ElementType = void;
}
}
unittest {
// Works for simple 1-element puts for structs.
struct Ham0(T) {
void put(T d) {}
}
assert(is(ElementType!(Ham0!float) == float));
// Works for classes too, which have array-based puts.
class Ham1(T) {
void put(T[] d) {}
}
assert(is(ElementType!(Ham1!float) == float));
// Distracting functions are ignored, and if single & array
// puts are supported, the element type is still correct.
struct Ham2(T) {
void put() {}
void put(float f, T[] d) {}
void put(T[] d) {}
void put(T d) {}
}
assert(is(ElementType!(Ham2!int) == int));
}