product(2)

Pythonではジェネレータでこういうことができます。

def gen():
    for n in range(3):
        for m in range(n + 1, 4):
            yield (n, m)
(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)

直積ではなく、mを出すiterableがnに依存しています。これを実現するにはどうすればよいでしょう。
それには、nに応じてmのiterableを返すような関数オブジェクトを引数にすればよいでしょう。こんな感じです。

auto    g = product(range(3), [](int n) { return range(n + 1, 4); });

前回の直積と同じ関数名にしています。以前同じようなことをやったときは、SFINAEで関数オブジェクトかどうか判別していましたが、今回はshared_ptrを使っているので単なるオーバーロードになっています。

template<typename T, typename U>
class cProduct : public cIterable<tuple<T,U>> {
    shared_ptr<cIterable<T>>    gen1;
    shared_ptr<cIterable<U>>    gen2;
    vector<U>   v;
    typename vector<U>::const_iterator  p;
    int     mode;
    
public:
    cProduct(shared_ptr<cIterable<T>> g1, shared_ptr<cIterable<U>> g2) :
                                            gen1(g1), gen2(g2), mode(0) { }
    
    bool exists_next() {
        if(mode == 0) {
            if(!gen1->exists_next())
                return false;
            else if(!gen2->exists_next())
                return false;
            
            mode = 1;
            v.push_back(gen2->value());
            return true;
        }
        else if(mode == 1) {
            if(gen2->exists_next()) {
                v.push_back(gen2->value());
                return true;
            }
            else if(gen1->exists_next()) {
                p = v.begin();
                mode = 2;
                return true;
            }
            else {
                return false;
            }
        }
        else {
            ++p;
            if(p != v.end()) {
                return true;
            }
            else if(gen1->exists_next()) {
                p = v.begin();
                return true;
            }
            else {
                return false;
            }
        }
    }
    tuple<T,U> value() const {
        T   y = mode == 1 ? gen2->value() : *p;
        return tuple<T,U>(gen1->value(), y);
    }
};

template<typename T, typename U>
shared_ptr<cIterable<tuple<T,U>>> product(shared_ptr<cIterable<T>> g1,
                                            shared_ptr<cIterable<U>> g2) {
    return shared_ptr<cIterable<tuple<T,U>>>(new cProduct<T,U>(g1, g2));
}

template<typename T, typename U, typename V>
class cDependentlyProduct : public cIterable<tuple<T,U>> {
    shared_ptr<cIterable<T>>    gen1;
    shared_ptr<cIterable<T>>    gen2;
    V       func;
    bool    first;
    
public:
    cDependentlyProduct(shared_ptr<cIterable<T>> g, V f) :
                                    gen1(g), func(f), first(true) { }
    
    bool exists_next() {
        if(first) {
            first = false;
            return next1();
        }
        else if(!gen2->exists_next())
            return next1();
        else
            return true;
    }
    tuple<T,U> value() const {
        return tuple<T,U>(gen1->value(), gen2->value());
    }
    
private:
    bool next1() {
        if(!gen1->exists_next())
            return false;
        gen2 = func(gen1->value());
        return gen2->exists_next();
    }
};

template<typename T, typename V>
auto product(shared_ptr<cIterable<T>> g, V f)
        -> shared_ptr<cIterable<tuple<T,decltype(f(g->value())->value())>>> {
    typedef decltype(f(g->value())->value())    U;
    auto    p = new cDependentlyProduct<T,U,V>(g, f);
    return shared_ptr<cIterable<tuple<T,U>>>(p);
}