python の for 文は C++ と比べてかなり充実しています。C++ でも同じような表記ができると便利だろうということで、C++17 の範囲 for と構造化束縛を組み合わせて、enumerate と zip を用いたループ処理を簡潔に実現するクラスを作成したので、公開します。
ただし、begin(), end() を持たない配列等には使えません。関数の返り値など、一時オブジェクトを渡すこともできません。→ 新記事で対応しました。 https://qiita.com/kkoba775/items/c916ede0c50ec9b7aa04
(1) zip を実現するクラス
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 を実現するクラス
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))
の代用です。