C ++ 11逆範囲ベースのforループ


321

イテレーターの方向を逆にするコンテナーアダプターがあるので、範囲ベースのforループでコンテナーを逆方向に反復できますか?

明示的なイテレータでこれを変換します:

for (auto i = c.begin(); i != c.end(); ++i) { ...

これに:

for (auto i = c.rbegin(); i != c.rend(); ++i) { ...

これを変換したい:

for (auto& i: c) { ...

これに:

for (auto& i: std::magic_reverse_adapter(c)) { ...

そのようなことはありますか、それとも自分で書く必要がありますか?


17
リバースコンテナーアダプターは興味深いように聞こえますが、自分で作成する必要があると思います。標準委員会が明示的なイテレータの代わりに範囲ベースのアルゴリズムを急いで適用する場合、この問題は発生しません。
deft_code

4
@deft_code:「の代わりに」イテレータベースのアルゴリズムを削除する理由は何ですか?beginto からまで反復しない場合endや、ストリームイテレータなどを処理する場合は、これらの方がはるかに優れており、冗長性が低くなります。範囲アルゴリズムは素晴らしいですが、反復アルゴリズムよりも(遅延評価の可能性を除いて)単なる構文上の砂糖です。
ニコルボーラス

17
@deft_code template<typename T> class reverse_adapter { public: reverse_adapter(T& c) : c(c) { } typename T::reverse_iterator begin() { return c.rbegin(); } typename T::reverse_iterator end() { return c.rend(); } private: T& c; };それは(追加向上させることができるconstバージョンなど)が、それが動作:vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;プリント321
セスカーネギー

10
@SethCarnegie:そして、素敵な関数型を追加するには:をtemplate<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}使用for(auto &i: reverse_adapt_container(v)) cout << i;して反復することができます。
Nicol Bolas、

2
@CR:順序が重要なループの簡潔な構文として使用できなくなるため、それ意味する必要があるとは思いません。IMOの簡潔さは、セマンティックな意味よりも重要/有用ですが、簡潔さを重視しない場合は、スタイルガイドで必要な意味を与えることができます。それはある種のparallel_for目的であり、何らかの形で標準に組み込まれた場合、「どの順序でもかまわない」という条件がさらに強くなります。もちろん、範囲ベースの構文糖も持つことができます:-)
スティーブジェソップ

回答:


230

実際、Boostにはそのようなアダプターがありますboost::adaptors::reverse

#include <list>
#include <iostream>
#include <boost/range/adaptor/reversed.hpp>

int main()
{
    std::list<int> x { 2, 3, 5, 7, 11, 13, 17, 19 };
    for (auto i : boost::adaptors::reverse(x))
        std::cout << i << '\n';
    for (auto i : x)
        std::cout << i << '\n';
}

90

実際、C ++ 14では、非常に数行のコードで実行できます。

これは、@ Paulのソリューションとアイデアがよく似ています。C ++ 11には欠けているものが原因で、そのソリューションは少し不必要に肥大化しています(さらにstdの匂いで定義されています)。C ++ 14のおかげで、より読みやすくすることができます。

重要な観察は、に依存することによって、その範囲をベースとするためのループ作品begin()及びend()範囲のイテレータを取得するために。ADLのおかげで、カスタムを定義したりbegin()end()のstd ::で名前空間を。

これは非常に単純なサンプルソリューションです。

// -------------------------------------------------------------------
// --- Reversed iterable

template <typename T>
struct reversion_wrapper { T& iterable; };

template <typename T>
auto begin (reversion_wrapper<T> w) { return std::rbegin(w.iterable); }

template <typename T>
auto end (reversion_wrapper<T> w) { return std::rend(w.iterable); }

template <typename T>
reversion_wrapper<T> reverse (T&& iterable) { return { iterable }; }

たとえば、これは魅力のように機能します。

template <typename T>
void print_iterable (std::ostream& out, const T& iterable)
{
    for (auto&& element: iterable)
        out << element << ',';
    out << '\n';
}

int main (int, char**)
{
    using namespace std;

    // on prvalues
    print_iterable(cout, reverse(initializer_list<int> { 1, 2, 3, 4, }));

    // on const lvalue references
    const list<int> ints_list { 1, 2, 3, 4, };
    for (auto&& el: reverse(ints_list))
        cout << el << ',';
    cout << '\n';

    // on mutable lvalue references
    vector<int> ints_vec { 0, 0, 0, 0, };
    size_t i = 0;
    for (int& el: reverse(ints_vec))
        el += i++;
    print_iterable(cout, ints_vec);
    print_iterable(cout, reverse(ints_vec));

    return 0;
}

期待どおりに印刷する

4,3,2,1,
4,3,2,1,
3,2,1,0,
0,1,2,3,

NOTE std::rbegin()std::rend()std::make_reverse_iterator()まだGCC-4.9に実装されていません。これらの例は標準に従って記述していますが、安定したg ++ではコンパイルできません。それでも、これらの3つの関数に一時スタブを追加するのは非常に簡単です。以下はサンプル実装ですが、完全ではありませんが、ほとんどの場合十分に機能します。

// --------------------------------------------------
template <typename I>
reverse_iterator<I> make_reverse_iterator (I i)
{
    return std::reverse_iterator<I> { i };
}

// --------------------------------------------------
template <typename T>
auto rbegin (T& iterable)
{
    return make_reverse_iterator(iterable.end());
}

template <typename T>
auto rend (T& iterable)
{
    return make_reverse_iterator(iterable.begin());
}

// const container variants

template <typename T>
auto rbegin (const T& iterable)
{
    return make_reverse_iterator(iterable.end());
}

template <typename T>
auto rend (const T& iterable)
{
    return make_reverse_iterator(iterable.begin());
}

35
数行のコード?私を許してください、しかしそれは10を超えています:-)
ジョニー

4
実際には、行のカウント方法に応じて5〜13になります。)回避策はライブラリの一部であるため、そこにあるべきではありません。ちなみに私に思い出させてくれてありがとう。この回答は、すべての余分な行がまったく不要な最近のコンパイラバージョンで更新する必要があります。
Prikso NAI

2
実装を忘れforward<T>たようですreverse
SnakE 2016年

3
うーん、これをヘッダーに入れると、ヘッダーに入るusing namespace stdので、お勧めできません。それとも何か不足していますか?
エスタン2017年

3
実際、「<anything>を使って」と書いてはいけません。ヘッダーのファイルスコープ。using宣言をbegin()およびend()の関数スコープに移動することにより、上記を改善できます。
クリスハートマン

23

これはブーストなしでC ++ 11で動作するはずです:

namespace std {
template<class T>
T begin(std::pair<T, T> p)
{
    return p.first;
}
template<class T>
T end(std::pair<T, T> p)
{
    return p.second;
}
}

template<class Iterator>
std::reverse_iterator<Iterator> make_reverse_iterator(Iterator it)
{
    return std::reverse_iterator<Iterator>(it);
}

template<class Range>
std::pair<std::reverse_iterator<decltype(begin(std::declval<Range>()))>, std::reverse_iterator<decltype(begin(std::declval<Range>()))>> make_reverse_range(Range&& r)
{
    return std::make_pair(make_reverse_iterator(begin(r)), make_reverse_iterator(end(r)));
}

for(auto x: make_reverse_range(r))
{
    ...
}

58
IIRCが名前空間stdに何かを追加することは、壮大な失敗への招待です。
BCS

35
「エピックフェイル」の規範的な意味はstdわかりませんが、名前空間で関数をオーバーロードすると、17.6.4.2.1に従って動作が未定義になります。
ケーシー

9
それはこの名前でC ++ 14にあるようです。
HostileForkはSEを信頼しないと

6
@MuhammadAnnaqeeb残念なことに、そうすると正確に衝突します。両方の定義でコンパイルすることはできません。プラスコンパイラが定義を持つことが必要とされていないではない C ++ 11の下に存在するだけでC ++ 14の下に表示されます(仕様は何について何も言っていないではないですまさに、のstd ::で名前空間を)。したがって、これは、標準に準拠したC ++ 11コンパイラでのコンパイルの失敗の可能性が非常に高くなります。C++ 14 にないランダムな名前の場合よりもはるかに可能性が高くなります。そして指摘したように、それは「未定義の振る舞い」です...そのため、コンパイルの失敗はそれが行う最悪の事態ではありません。
HostileForkがdont信託SEと言う

2
@HostileFork名前の衝突make_reverse_iteratorはなく、std名前空間にもないため、C ++ 14バージョンとは衝突しません。
Paul Fultz II

11

これはあなたのために働きますか:

#include <iostream>
#include <list>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/iterator_range.hpp>

int main(int argc, char* argv[]){

  typedef std::list<int> Nums;
  typedef Nums::iterator NumIt;
  typedef boost::range_reverse_iterator<Nums>::type RevNumIt;
  typedef boost::iterator_range<NumIt> irange_1;
  typedef boost::iterator_range<RevNumIt> irange_2;

  Nums n = {1, 2, 3, 4, 5, 6, 7, 8};
  irange_1 r1 = boost::make_iterator_range( boost::begin(n), boost::end(n) );
  irange_2 r2 = boost::make_iterator_range( boost::end(n), boost::begin(n) );


  // prints: 1 2 3 4 5 6 7 8 
  for(auto e : r1)
    std::cout << e << ' ';

  std::cout << std::endl;

  // prints: 8 7 6 5 4 3 2 1
  for(auto e : r2)
    std::cout << e << ' ';

  std::cout << std::endl;

  return 0;
}

7
template <typename C>
struct reverse_wrapper {

    C & c_;
    reverse_wrapper(C & c) :  c_(c) {}

    typename C::reverse_iterator begin() {return c_.rbegin();}
    typename C::reverse_iterator end() {return c_.rend(); }
};

template <typename C, size_t N>
struct reverse_wrapper< C[N] >{

    C (&c_)[N];
    reverse_wrapper( C(&c)[N] ) : c_(c) {}

    typename std::reverse_iterator<const C *> begin() { return std::rbegin(c_); }
    typename std::reverse_iterator<const C *> end() { return std::rend(c_); }
};


template <typename C>
reverse_wrapper<C> r_wrap(C & c) {
    return reverse_wrapper<C>(c);
}

例えば:

int main(int argc, const char * argv[]) {
    std::vector<int> arr{1, 2, 3, 4, 5};
    int arr1[] = {1, 2, 3, 4, 5};

    for (auto i : r_wrap(arr)) {
        printf("%d ", i);
    }
    printf("\n");

    for (auto i : r_wrap(arr1)) {
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

1
回答の詳細を説明していただけますか?
モスタフィズ2016

これは、逆の範囲ベースのループC ++ 11クラスタムプレートです
Khan Lau

4

あなたが使用できる場合は範囲V3を、あなたは、リバースレンジアダプタを使用することができますranges::view::reverseますが、逆にコンテナを表示することができます。

最小限の実用的な例:

#include <iostream>
#include <vector>
#include <range/v3/view.hpp>

int main()
{
    std::vector<int> intVec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto const& e : ranges::view::reverse(intVec)) {
        std::cout << e << " ";   
    }
    std::cout << std::endl;

    for (auto const& e : intVec) {
        std::cout << e << " ";   
    }
    std::cout << std::endl;
}

DEMO 1を参照してください。

注: 1としてエリックNiebler、この機能はで利用できるようになります++ 20 C。これは<experimental/ranges/range>ヘッダーで使用できます。その後、forステートメントは次のようになります。

for (auto const& e : view::reverse(intVec)) {
       std::cout << e << " ";   
}

デモ2を参照


更新:ranges::view名前空間の名前がに変更されましたranges::views。したがって、を使用しますranges::views::reverse
nac001

2

C ++ 14を使用していない場合、以下の最も簡単な解決策を見つけます。

#define METHOD(NAME, ...) auto NAME __VA_ARGS__ -> decltype(m_T.r##NAME) { return m_T.r##NAME; }
template<typename T>
struct Reverse
{
  T& m_T;

  METHOD(begin());
  METHOD(end());
  METHOD(begin(), const);
  METHOD(end(), const);
};
#undef METHOD

template<typename T>
Reverse<T> MakeReverse (T& t) { return Reverse<T>{t}; }

デモ。関数
を持たないコンテナー/データ型(配列など)ではbegin/rbegin, end/rend機能しません。


0

あなたは単にBOOST_REVERSE_FOREACHそれを逆方向に反復して使うことができます。たとえば、コード

#include <iostream>
#include <boost\foreach.hpp>

int main()
{
    int integers[] = { 0, 1, 2, 3, 4 };
    BOOST_REVERSE_FOREACH(auto i, integers)
    {
        std::cout << i << std::endl;
    }
    return 0;
}

次の出力を生成します。

4

3

2

1

0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.