Detecting ElementType of OutputRange

Vijay Nayar madric at gmail.com
Sat Feb 26 11:40:39 UTC 2022


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:

``` d
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));
}
```


More information about the Digitalmars-d-learn mailing list