【C++】Pythonのenumerate()関数(っぽいもの)を実装する

C++

はじめに

前のエントリでは、Pythonのzip()関数に似た機能を持つ関数を作成しました。これにより、1つの範囲for文で複数のコンテナを一度に扱えるようになりました。

一方、範囲for文は全要素にアクセスできますが、現在の要素が何番目の要素か知ることができません。このためには、以下のように範囲for文使わない方法で書く必要があります。

std::vector<int> a = [1, 2, 3];

for(size_t i = 0; i < a.size(); i++){
    std::cout << a[i] << std::endl;
}

せっかくイテレータがあるのにイテレータを使わないという状況になります。

Pythonなどを使っていると、以下のようにインデックスも一緒に扱えるenumerate()関数の存在が便利に感じることがあります。

a = [1, 2, 3]

for i, elem in enumerate(a):
  print(f"{i}: {elem}")

これもC++17で追加された構造化束縛を使えば、似たようなことができそうです。そこで、本エントリでは、タプルを使用してインデックスと要素をまとめて扱うEnumeratorクラスとEnumeratorクラスのインスタンスを返すEnumerate()関数を作成してみました。

実装と使用例

実装

実際の実装はGitHubにアップしたソースコードをご覧ください。

Zipperクラスと同様に範囲for文を適用できるようにするため、コンテナのメンバ関数としてbegin()end()を実装し、さらにイテレータも実装しています。イテレータの*演算子の戻り値は、インデックスとコンテナの要素のタプルとなります。こちらのエントリで作成したGetReference<>を使っているため、配列にも対応しています。

GitHubにアップしたコードでは、前エントリで作成したZipperクラスも扱えるようにするため、特殊化したEnumeratorクラスも実装してあります。こちらでは、メンバ変数としてZipperクラスのオブジェクトの(参照ではなく)コピーを保持することで、Enumerate(Zipper &&)と、右辺値参照で受けられるようにしてあります。

使用例

以下に使用例を示します。zipper.h, enumerator.hが同じディレクトリにあるとします。(zipper.henumerator.hの中でインクルードされています)

#include <iostream>
#include <list>
#include <map>
#include <string>
#include <vector>

#include "enumerator.h"

int main()
{
    std::vector<int> a = {1, 2, 3};
    std::list<std::string> b = {"a", "b", "c"};
    std::map<int, std::string> c = {{0, "abc"}, {1, "def"}, {2, "ghi"}};

    for (auto [i, s, t, u] : Enumerate(Zip(a, b, c))) {
      std::cout << "[" << i << "] " << s << " " << t << " " << u.first << " " << u.second << std::endl;
    }

    return 0;
}

まとめ

タプルを利用してPython風のEnumerate()関数を自作してみました。1つの範囲for文でインデックスと要素をまとめて扱えるようになります。また、Zip()関数を組み合わせれば複数のコンテナの各要素とインデックスをまとめて扱うことも可能です。

なお、C++23ではstd::views::enumerate()関数<ranges>で標準化されています。C++23が使える環境ではそちらを使ったほうが良いです。

std::vector<int> a = {1, 2, 3};
std::list<std::string> b = {"aaa", "bbb", "ccc"};

for(auto & [i, p, q]: std::views::enumerate(std::views::zip(a, b))) {
    std::println("{}: {}, {}", i, p, q);
}

C++

Posted by izadori