C ++ 11 역 범위 기반 for-loop


321

범위 기반 for-loop와 반대로 컨테이너를 반복 할 수 있도록 반복자의 방향을 바꾸는 컨테이너 어댑터가 있습니까?

명시 적 반복자를 사용하여 이것을 변환합니다.

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 : "대신에?" 반복자 기반 알고리즘을 제거하려는 이유는 무엇입니까? 그들은 훨씬 더 적은 당신이에서 반복 처리를하지 않는 경우에 대한 자세한 것 begin까지 end, 또는 스트림 반복자 등을 다루는. 범위 알고리즘은 훌륭하지만 반복기 알고리즘에 비해 실제로 구문 설탕입니다 (지연 평가 가능성 제외).
Nicol Bolas

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;prints321
Seth Carnegie

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어떤 형태로 표준에 포함된다면 훨씬 더 강한 "나는 어떤 순서를 신경 쓰지 않는다"는 조건이 될 것이다. 물론 그것은 범위 기반의 구문 설탕도 가질 수 있습니다 :-)
Steve Jessop

답변:


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에서 누락 된 것이 있기 때문에이 솔루션은 불필요하게 부풀어 오른 것입니다 (표준 냄새로 정의). 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,

std::rbegin() , std::rend()그리고 std::make_reverse_iterator()아직 GCC-4.9에서 구현되지 않습니다. 이 예제를 표준에 따라 작성하지만 안정적인 g ++로 컴파일하지는 않습니다. 그럼에도 불구하고이 세 가지 기능에 임시 스텁을 추가하는 것은 매우 쉽습니다. 다음은 샘플 구현입니다. 확실히 완료되지는 않았지만 대부분의 경우에는 잘 작동합니다.

// --------------------------------------------------
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
몇 줄의 코드? 나를 용서하지만 열 이상입니다 :-)
Jonny

4
실제로 줄 수를 세는 방법에 따라 5-13입니다.) 해결 방법은 라이브러리의 일부이므로 거기에 있으면 안됩니다. btw를 상기시켜 주셔서 감사합니다.이 답변은 모든 추가 라인이 전혀 필요하지 않은 최신 컴파일러 버전에 대해 업데이트해야합니다.
Prikso NAI

2
구현 forward<T>에서 잊어 버린 reverse것 같습니다.
SnakE

3
흠, 이것을 머리글에 넣으면 using namespace std머리글에 들어 있으므로 좋지 않습니다. 아니면 뭔가 빠졌습니까?
estan

3
실제로 "<anything>;"을 쓰면 안됩니다. 헤더의 파일 범위에서. using 선언을 begin () 및 end ()의 함수 범위로 이동하여 위의 내용을 개선 할 수 있습니다.
Chris Hartman

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에 따라 동작이 정의되지 않습니다.
Casey

9
이 이름 으로 C ++ 14에 분명히 있습니다.
HostileFork는 SE

6
@MuhammadAnnaqeeb 불행한 점은 그렇게하는 것이 정확히 충돌한다는 것입니다. 두 가지 정의로 컴파일 할 수 없습니다. 게다가 컴파일러는 정의가 필요하지 않습니다 하지 (11) C ++에서 존재 만 C ++ (14) 아래에 나타납니다 (사양은 무엇에 대해 아무것도 말하지 않는다 없는 단지 무엇의 표준 : : 네임 스페이스를). 따라서 이것은 표준 호환 C ++ 11 컴파일러에서 컴파일 실패 일 가능성이 높습니다 . C ++ 14 에없는 임의의 이름보다 훨씬 더 가능성이 높습니다 ! 그리고 지적한 바와 같이, 그것은 "정의되지 않은 행동"입니다 ... 그래서 컴파일에 실패하는 것이 최악이 아닙니다.
HostileFork는 SE

2
@HostileFork에 이름 충돌이 없습니다, make_reverse_iterator에없는 std그것의 14 버전 ++ C와 충돌하지 않도록, 이름 공간.
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
답변에 대해 더 자세히 설명해 주시겠습니까?
Mostafiz

이것은 리버스 레인지베이스 루프 C ++ 11 클래스 tamplate입니다
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;
}

데모 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.