1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2023-12-31

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

ただし、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);
}

使用例

#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 の方がいい場合もあるので、テンプレート引数にしました。(関数の方は用意していない。)

使用例

#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 であれば

#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 では、関数オブジェクトを用いて

#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 が導入され、一時オブジェクトも延命されるので、それまでの繋ぎです。

(4) CMake 対応

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

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?