module cipher; private import std.range : isInputRange, isForwardRange, ElementType, InputRange, ForwardRange, inputRangeObject; private import std.string : format; private import salsa20; private import chacha20; public: enum Cipher { salsa20, chacha20 } mixin(cipherFunctionString!(InputRange, isOnlyInputRange)); mixin(cipherFunctionString!(ForwardRange, isForwardRange)); unittest { import std.array; ubyte[32] key; ubyte[8] nonce; ubyte[] a = [1, 2, 3]; auto b = a.cipherFunction(key, nonce, Cipher.salsa20); InputRange!ubyte c = a.inputRangeObject; auto d = c.cipherFunction(key, nonce, Cipher.salsa20); static assert(isForwardRange!(typeof(b))); static assert(isInputRange!(typeof(d)) && !(isForwardRange!(typeof(d)))); } private: template isOnlyInputRange(R) { enum isOnlyInputRange = isInputRange!R && !(isForwardRange!R); } enum cipherFunctionString(alias ReturnType, alias Constraint) = q{ %s!(ElementType!R) cipherFunction(R)(R range, ubyte[32] key, ubyte[8] nonce, Cipher cipher) if (is(ElementType!R : ubyte) && %s!R) { final switch (cipher) { case Cipher.salsa20: return range.salsa20Cipher(key, nonce).inputRangeObject; case Cipher.chacha20: return range.chacha20Cipher(key, nonce).inputRangeObject; } } }.format(__traits(identifier, ReturnType), __traits(identifier, Constraint));