<div>While this is range based, I had something different in mind.</div><div>(Not these function names, just the basic idea of the signatures)</div><div><br></div><div>struct Base64Encoder(Range) if (isInputRange!Range &amp;&amp; is(ElementType!Range == ubyte))<br>
{</div><div>    // range methods</div><div>   @property char front() { ... }</div><div>   void popFront() { ... }</div><div>   @property size_t length() { ... }</div><div>   ...<br>};</div><div>Base64Encoder!(Range) Base64Encode(Range)(Range r) if (isInputRange!Range &amp;&amp; is(ElementType!Range == ubyte))</div>
<div>{</div><div>    return Base64Encoder!Range(r);</div><div>}<br></div><div><br></div><div>This way encoding would convert an input range of ubyte to an input range of char, and decoding would convert Range!char to Range!ubyte.</div>
<div><br></div><div>This way you would be able to use it with std.algorithm, std.range etc.</div><div>When called with an array the range would be able to provide length and be bidirectional.</div><div>This way there would be no allocations inside the range at all.</div>
<div><br></div><div>You could create and fill a buffer using</div><div>auto buffer = array(encode(data));</div><div>or fill an existing buffer using</div><div>copy(encode(data), buffer);</div><div><br></div><div>At this point I don&#39;t know if everything would be better off using char or dchar ranges.</div>
<div><br></div><div>@Andrei</div><div>Is it an improvement for the range to yield char[] instead of char?</div><div><br></div><div>@Sean</div><div>Would copy(decode(buffer), buffer) cover your use case sufficiently?</div>