I'm writing my own map function modeled after the one in phobos. (because I feel like it, that's why. good learning experience.) I've encountered one remarkable difference: The phobos function accepts arrays and mine does not. I understand why - I'm calling methods that arrays don't have - but what I don't understand is why the phobos function _does_ work. I haven't been able to find what in the phobos code accounts for iterables that aren't ranges.

What am I missing?


    enum canMap(T) = isInputRange!(Unqual!T);

    auto map(alias func, Range)(Range range) if(canMap!Range){
        return Mapping!(func, Range)(range);
    }

    struct Mapping(alias func, Range) if(canMap!Range){

        alias URange = Unqual!Range;
        Range input;

        this(URange input){
            this.input = input;
        }

        void popFront(){
            this.input.popFront();
        }
        @property auto ref front(){
            return func(this.input.front);
        }

        static if(isBidirectionalRange!URange){
            @property auto ref back(){
                return func(this.input.back);
            }
            void popBack(){
                this.input.popBack();
            }
        }

        static if(isInfinite!URange){
            enum bool empty = false;
        }else{
            @property bool empty(){
                return this.input.empty;
            }
        }

        static if(isRandomAccessRange!URange){
            static if(is(typeof(URange.opIndex) == function)){
                alias Index = Parameters!(URange.opIndex)[0];
            }else{
                alias Index = size_t;
            }
            auto ref opIndex(Index index){
                return func(this.input[index]);
            }
        }

        static if(is(typeof(URange.opDollar))){
            alias opDollar = URange.opDollar;
        }

        static if(hasLength!URange){
            @property auto length(){
                return this.input.length;
            }
        }

        static if(hasSlicing!URange){
            static if(is(typeof(URange.opIndex) == function)){
                alias SliceIndex = Parameters!(URange.opIndex)[0];
            }else{
                alias SliceIndex = size_t;
            }
            auto opSlice(SliceIndex low, SliceIndex high){
                return typeof(this)(this.input[low .. high]);
            }
        }

        static if(isForwardRange!URange){
            @property auto save(){
                return typeof(this)(this.input.save);
            }
        }

    }

    version(unittest) import mach.error.unit;
    unittest{
        import std.stdio;
        //import std.algorithm : map; // Works with this

        // no property 'popFront', etc for type 'int[]'
        writeln(
            [1, 2, 3].map!((item) => (item * item))
        );
    }

Tangentially related question - Why does phobos use isInputRange!(Unqual!T) instead of just isInputRange!T? What's the functional difference here?

Reply via email to