C++ で python の enumerate, zip 表記を使う。

python の for 文は C++ と比べてかなり充実しています。C++ でも同じような表記ができると便利だろうということで、C++17 の範囲 for と構造化束縛を組み合わせて、enumerate と zip を用いたループ処理を簡潔に実現するクラスを作成したので、公開します。

https://cpprefjp.github.io/lang/cpp17/generalizing_the_range-based_for_loop.html

https://cpprefjp.github.io/lang/cpp17/structured_bindings.html

ただし、begin(), end() を持たない配列等には使えません。関数の返り値など、一時オブジェクトを渡すこともできません。→ 新記事で対応しました。 https://qiita.com/kkoba775/items/c916ede0c50ec9b7aa04

# (1) zip を実現するクラス
```zip.hpp
template<class T1, class T2>
struct Zip {
    T1 &c1_;
    T2 &c2_;

    using iterator1 = decltype(c1_.begin());
    using iterator2 = decltype(c2_.begin());
    using terminator1 = decltype(c1_.end());
    using terminator2 = decltype(c2_.end());

    Zip(T1 &c1, T2 &c2) : c1_(c1), c2_(c2) {}

    struct terminator {
        terminator1 t1_;
        terminator2 t2_;
    };

    struct iterator {
        iterator1 i1_;
        iterator2 i2_;

        using V1 = decltype(*i1_);
        using V2 = decltype(*i2_);

        bool operator!=(terminator const& a) const {
            return (i1_ != a.t1_) && (i2_ != a.t2_);
        }

        std::pair<V1&, V2&> operator*() const {
            return {*i1_, *i2_};
        }

        iterator& operator++() {
            ++i1_;
            ++i2_;
            return *this;
        }
    };

    iterator begin() {
        return {c1_.begin(), c2_.begin()};
    }

    terminator end() {
        return {c1_.end(), c2_.end()};
    }
};

template<class T1, class T2>
inline auto zip(T1 &a, T2 &b) {
    return Zip<T1, T2>(a, b);
}
```
使用例
```C++
#include <iostream>
#include <vector>
#include <set>
#include <string>

#ifndef __cpp_structured_bindings
#error C++17 is required for the structured_bindings feature.
#endif

#include "zip.hpp"

int main() {

    std::vector<int> A = {1, -2, 4};
    std::set<std::string> B;
    B.insert("a");
    B.insert("z");
    B.insert("b");

    for (auto [n, s] : zip(A, B)) {
        std::cout << n << ", " << s << std::endl;
    }    

    return 0;
}
```
n, s に A, B の要素の参照が順番に渡されてループします。
A, B のサイズが違う場合には、少ない方に合わせてループを終了します。


# (2) enumerate を実現するクラス
```enumerate.hpp
template<class T, class Int = int>
struct Enumerate {
    T &c_;
    Enumerate(T &c) : c_(c) {}

    using Iterator = decltype(c_.begin());
    using Terminator = decltype(c_.end());

    struct iterator {
        Iterator i_;
        Int c_ = 0;

        using Value = decltype(*i_);

        iterator(Iterator i) : i_(i) {}

        bool operator!=(Terminator const& a) const {
            return (i_ != a);
        }

        std::pair<Int, Value&> operator*() const {
            return {c_, *i_};
        }

        iterator& operator++() {
            ++i_;
            ++c_;
            return *this;
        }
    };

    auto begin() {
        return iterator(c_.begin());
    }

    auto end() {
        return c_.end();
    }
};

template<class T>
inline auto enumerate(T &a) {
    return Enumerate<T>(a);
}
```
カウンタは ```int``` よりも ```size_t``` の方がいい場合もあるので、テンプレート引数にしました。（関数の方は用意していない。）

使用例
```C++
#include <iostream>
#include <set>
#include <string>

#ifndef __cpp_structured_bindings
#error C++17 is required for the structured_bindings feature.
#endif

#include "enumerate.hpp"

int main() {

    std::set<std::string> B;
    B.insert("a");
    B.insert("z");
    B.insert("b");

    for (auto [i, s] : enumerate(B)) {
        std::cout << "[" << i << "] " << s << std::endl;
    }

    return 0;
}
```
i にはループカウンタの値 0, 1, 2 が、s には B の要素の参照が順に渡されてループします。 

Visual Studio 2022, GCC 9.4.0 で動作確認。

# (3) 一時オブジェクト使用に関する制限

enumerate() や zip() に関数の返り値などの一時オブジェクトを渡すことはできません。
C++20 であれば
```C++
#include <iostream>
#include <vector>

#include "enumerate.hpp"

std::vector<int> func() {
    return {1, 2, 3};
}

int main() {

    for (auto &&F = func(); auto [i, n] : enumerate(F)) {
        std::cout << i << " = " << n << std::endl;
    }

    return 0;
}
```
として回避できます。
C++17 では、関数オブジェクトを用いて
```C++
#include <iostream>
#include <vector>

#include "enumerate.hpp"

struct Functor {
    std::vector<int> r;
    std::vector<int>& operator()(int n) {
        r.resize(n);
        for (int i=0; i < n; i++) r[i] = i;
        return r;
    }
};

int main() {
    Functor func;
    for (auto [i, n] : enumerate(func(5))) {
        std::cout << i << " = " << n << std::endl;
    }

    return 0;
}
```
とする等して、関数の返り値の存在を確保する必要があります。


なお、C++23 では zip_view が導入され、一時オブジェクトも延命されるので、それまでの繋ぎです。

https://cpprefjp.github.io/reference/ranges/zip_view.html

https://cpprefjp.github.io/lang/cpp23/lifetime_extension_in_range_based_for_loop.html


# (4) CMake 対応
ヘッダファイル１つのみのライブラリではありますが、CMake から利用できるようにしました。

https://github.com/kkoba705/ZipEnum

zip() は３つまで、enumerate() は２つまで指定できるように拡張しています。
```enumerate(A, B)``` は ```enumerate(zip(A, B))``` の代用です。