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

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

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

前回の記事 https://qiita.com/kkoba775/items/1849eaecf47789be4921 では関数の返り値など、一時オブジェクトを渡すことができませんでしたが、これを可能にしました。

# (1) zip を実現するクラス
```zip.hpp
#include <utility>
#include <type_traits>

template<class T1, class T2>
struct Zip {
    T1 c1_;
    T2 c2_;

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

    Zip(T1 && c1, T2 && c2)  : 
        c1_(std::forward<T1>(c1)), c2_(std::forward<T2>(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;
        }
    };

    auto begin() {
        return iterator {std::begin(c1_), std::begin(c2_)};
    }

    auto end() {
        return terminator {std::end(c1_), std::end(c2_)};
    }
};

template<class T1, class T2>
inline auto zip(T1 && a, T2 && b) {
    return Zip<T1, T2>(std::forward<T1>(a), std::forward<T2>(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 のサイズが違う場合には、少ない方に合わせてループを終了します。

zip() の引数が左辺値参照（実体）だった場合には、Zip のメンバ変数に参照変数が用意されます。引数が右辺値参照（一時オブジェクト）だった場合には、Zip のメンバ変数に実変数が用意されて、ムーブコンストラクタによって初期化されます。

# (2) enumerate を実現するクラス
```enumerate.hpp
#include <utility>
#include <type_traits>

template<class T, class Int = int>
struct Enumerate {
    T c_;

    explicit Enumerate(T && c) : c_(std::forward<T>(c)) {}

    using Iterator = decltype(std::begin(c_));
    using Terminator = decltype(std::end(c_));

    struct iterator {
        Iterator i_;
        Int c_ = 0;

        using Value = decltype(*i_);

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

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

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

    auto begin() {
        return iterator {std::begin(c_), 0};
    }

    auto end() {
        return std::end(c_);
    }
};

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

C++17 から、範囲for に渡すコンテナ(今回は Enumerate<>) の begin() と end()で型の不統一が許されているので、begin() が返す反復子にのみカウンタを用意しています。

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


使用例
```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` の要素が順に渡されてループします。 `std::set<>` の内容は昇順に読み出されるので、出力は ```"a", "b", "z"``` の順になります。`s` の型は `const std::string&` なので、これを介して `B` の内容を書き換えることはできません。

# (3) 一時オブジェクトの使用

C++23 より前では範囲 for の右側の引数が参照によって一時変数を受け取っていても、一時変数の寿命の引き伸ばしが行われません。

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

このために、前回のバージョンでは enumerate() や zip() に一時オブジェクト（関数の返り値など）を渡すことができませんでした。今回は、右辺値参照を移動によって保管しているので、関数の返り値を渡したり、 zip や enumerate を入れ子にして使うことができます。

```C++
#include <iostream>
#include <vector>

#include "zip.hpp"
#include "enumerate.hpp"

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

int main() {
    double D[] = {1.0, -2.1, 3.14};

    for (auto [i, z] : enumerate(zip(D, iota(5)))) {
        auto [a, n] = z;
        std::cout << i << " = " << n << ", " << a << std::endl;
    }
    return 0;
}
```

# (4) 可変数入力への対応

https://github.com/CommitThis/zip-iterator

を参考にして、複数の入力に対応しました。上記に比べて簡素な実装になっていますが、実用上は問題ないと思います。

また、enumerate() に複数の入力ができるようにしてあります。enumerate(A, B) は enumerate(zip(A, B)) の代わりになります。

```zipn.hpp
#include <tuple>

template<typename ... T>
struct ZipN {
    std::tuple<T...> c_;

    using iterators = std::tuple<decltype(std::begin(std::declval<T&>())) ...>;
    using terminators = std::tuple<decltype(std::end(std::declval<T&>())) ...>;
    using values = std::tuple<decltype(*std::begin(std::declval<T&>())) ...>;

    ZipN(T && ... c)  : c_{std::forward<T>(c) ...} {}

    struct iterator {
        iterators i_;

        template<size_t ... Index>
        bool ok(terminators const& e, std::index_sequence<Index...>) const {
            return ((std::get<Index>(i_) != std::get<Index>(e)) && ...);
        }

        bool operator!=(terminators const& a) const {
            return ok(a, std::index_sequence_for<T...>{});
        }

        auto operator*() const {
            return std::apply([](auto && ... args){
                return values{*args...};
            }, i_);
        }

        iterator& operator++() {std::apply([](auto && ... args){
                ((++args), ...); 
            }, i_);
            return *this;
        }
    };

    auto begin() {
        return iterator{std::apply([](auto && ... args){
            return iterators{std::begin(args)...};
        }, c_)};
    }

    auto end() {
        return std::apply([](auto && ... args){
            return terminators{std::end(args)...};
        }, c_);
    }
};

template<typename ... T>
inline auto zip(T && ... t) {
    return ZipN<T...> {std::forward<T>(t)...};
}

template<typename Int = int>
struct counter {
    struct iterator {
        Int c_ = 0;
        auto operator*() const {return c_;}
        void operator++() {++c_; }
        bool operator!=(iterator) const {return true;}
    };
    auto begin() const {return iterator{0};}
    auto end() const {return iterator{};}
};

template<typename ... T>
inline auto enumerate(T && ... t) {
    return ZipN<counter<>, T...> {{}, std::forward<T>(t)...};
}
```
使用例
```C++
#include <vector>
#include <iostream>

#include "zipn.hpp"

int main() {
    const int a[] = {1, 2, 3, 4, 5, 6};
    auto b = std::vector<int>(std::begin(a), std::end(a));
    auto c = std::vector<int>(5);

    for (auto && [x, y, z] : zip(a, b, c)) {
        z = x + y;
    }

    for (auto [i, x, y] : enumerate(b, c)) {
        std::cout << i << ":" << x << ", " << y << std::endl;
    }
    return 0;
}
```
ループの回数はサイズが最小の c にあわせられるので、出力は 
```
0:1, 2
1:2, 4
2:3, 6
3:4, 8
4:5, 10
``` 
となります。

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

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

https://github.com/kkoba705/ZipEnum