44

重複の可能性:
C ++ 11の範囲ベースのforループで要素の位置を見つけますか?

私はを持っており、vectorそれを繰り返し、同時に、個々の要素のインデックスにアクセスしたいと思います(要素とそのインデックスの両方を関数に渡す必要があります)。私は次の2つの解決策を検討しました。

std::vector<int> v = { 10, 20, 30 };

// Solution 1
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx)
    foo(v[idx], idx);

// Solution 2
for (auto it = v.begin(); it != v.end(); ++it)
    foo(*it, it - v.begin());

もっとコンパクトな解決策があるのではないかと思っていました。Pythonの列挙に似たもの。これは、C ++ 11範囲ループを使用して取得したものに最も近いものですが、プライベートスコープでループの外側にインデックスを定義する必要があることは、1または2よりも悪い解決策のように思えます。

{
    int idx = 0;
    for (auto& elem : v)
        foo(elem, idx++);
}

インデックスがループに自己完結するように最新の例を単純化する方法(おそらくBoostを使用)はありますか?

4

3 に答える 3

28

これは、遅延評価を使用したある種の面白い解決策です。まず、ジェネレータオブジェクトを作成します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左辺値参照の両方、および右辺値参照で機能するはずですが、反復可能なオブジェクト全体を数回コピーすることを考えると、後者には実際のコストがかかります。この問題は、追加の調整で確実に解決できます。

forC ++ 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&&およびvalueasとして分解しdouble&ます。

于 2012-07-04T12:36:27.123 に答える
23

@Kosが言うように、これは非常に単純なことなので、これをさらに単純化する必要はなく、個人的には、インデックスを使用する従来のforループに固執するだけです。ただし、私は捨てstd::vector<T>::size_typeて単純に使用しますstd::size_t

for(std::size_t i = 0; i < v.size(); ++i)
    foo(v[i], i);

私は解決策2にあまり熱心ではありません。それは(ちょっと隠された)ランダムアクセスイテレータを必要とし、イテレータの長所の1つであるコンテナを簡単に交換することはできません。イテレータを使用して汎用にしたい場合(およびイテレータがランダムアクセスでないstd::distance場合にパフォーマンスが低下する可能性がある場合)は、次を使用することをお勧めします。

for(auto it(v.begin()); it != v.end(); ++it)
    foo(*it, std::distance(it, v.begin());
于 2012-07-04T11:51:38.520 に答える
1

1つの方法は、ループを独自の関数でラップすることです。

#include <iostream>
#include <vector>
#include <string>

template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
   for(int i = 0; i < vec.size(); i++) 
       fun(vec[i], i); 
}

int main() {
   std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
   mapWithIndex(vec, [](std::string s, int i){
      std::cout << i << " " << s << '\n';
   } );
}
于 2012-07-04T11:46:09.073 に答える