これは、遅延評価を使用したある種の面白い解決策です。まず、ジェネレータオブジェクトを作成しますenumerate_object
。
template<typename Iterable>
class enumerate_object
{
private:
Iterable _iter;
std::size_t _size;
decltype(std::begin(_iter)) _begin;
const decltype(std::end(_iter)) _end;
public:
enumerate_object(Iterable iter):
_iter(iter),
_size(0),
_begin(std::begin(iter)),
_end(std::end(iter))
{}
const enumerate_object& begin() const { return *this; }
const enumerate_object& end() const { return *this; }
bool operator!=(const enumerate_object&) const
{
return _begin != _end;
}
void operator++()
{
++_begin;
++_size;
}
auto operator*() const
-> std::pair<std::size_t, decltype(*_begin)>
{
return { _size, *_begin };
}
};
次に、テンプレート引数を推測してジェネレーターを返すラッパー関数enumerateを作成します。
template<typename Iterable>
auto enumerate(Iterable&& iter)
-> enumerate_object<Iterable>
{
return { std::forward<Iterable>(iter) };
}
これで、関数を次のように使用できます。
int main()
{
std::vector<double> vec = { 1., 2., 3., 4., 5. };
for (auto&& a: enumerate(vec)) {
size_t index = std::get<0>(a);
double& value = std::get<1>(a);
value += index;
}
}
上記の実装は単なるおもちゃです。左辺値参照const
と非const
左辺値参照の両方、および右辺値参照で機能するはずですが、反復可能なオブジェクト全体を数回コピーすることを考えると、後者には実際のコストがかかります。この問題は、追加の調整で確実に解決できます。
for
C ++ 17以降、分解宣言を使用すると、Pythonのようなクールな構文を使用して、初期化子でインデックスと値に直接名前を付けることもできます。
int main()
{
std::vector<double> vec = { 1., 2., 3., 4., 5. };
for (auto&& [index, value] : enumerate(vec)) {
value += index;
}
}
C ++準拠のコンパイラは、auto&&
推論index
をasstd::size_t&&
およびvalue
asとして分解しdouble&
ます。