Following the recent thread "front evaluated multiple time with joiner depending on where extra arg given", I'd like to propose the following addition in std.range :

The goal is to ensure that a given range's 'front' method is called only once per element, allowing one to handle safely side effects in 'front' methods

eg use case: lambdas with side effects given to a map/reduce/filter function, etc.

import std.traits;
import std.range;
struct CacheFront(R){
  alias T=ElementType!R;
  R a;
  import util.traits;
  enum isRef=mixin(isLvalue(q{a.front}));
  static if(isRef)
    T* ai;
  else{
    T ai;    
    bool isValid=false;
  }
  auto ref front(){
    //TODO: could also depend on whether front is pure
    static if(isRef){
      if(ai)
        return *ai;
      else{
        ai=&a.front;
        return *ai;
      }
    }
    else{
      if(isValid){
        return ai;
      }
      else{
        isValid=true;
        ai=a.front;
        return ai;
      }      
    }
  }
  void popFront(){
    a.popFront;
    static if(isRef)
      ai=null;
    else
      isValid=false;
  }
  //forward other properties automatically:
  alias a this;
}
auto cacheFront(R)(R a) if(isInputRange!R){
  return CacheFront!R(a);
}

//helper function (should be in phobos' std.traits)
void requireLvalue(T)(ref T);
string isLvalue(string a){
return `__traits(compiles, requireLvalue(`~a~`))`;
}

void main(){
  import std.algorithm;
  import std.array;
  {
    //checks that it calls front only once per element
    uint counter;
    auto b=[1,2,3].map!((a){counter++; return [a];}).cacheFront.joiner.array;
    assert(counter==3);
    assert(b==[1,2,3]);
  }
  {
    int counter=0;
    auto b=[1,2,3].map!((a){counter++; return [a];}).joiner.array;
    assert(counter==6);
  }
 {
    //checks that it works with ref front
    auto a0=[1,2,3];
    auto ref fun0(ref int a){a=0; return a;}
    auto b=a0. cacheFront.map!fun0.array;
    assert(b==[0,0,0]);
    assert(a0==[0,0,0]);

    //checks that it forwards properties of range:
    assert([1,2,3]. cacheFront.length==3);
  }
}


And a side question: is there a way to denote that a lambda can return by ref?