C ++ 11 범위 기반 for 루프에서 요소의 위치를 ​​찾으십니까?


79

다음 코드가 있다고 가정합니다.

vector<int> list;
for(auto& elem:list) {
    int i = elem;
}

elem별도의 반복기를 유지하지 않고 벡터에서 의 위치를 ​​찾을 수 있습니까 ?


17
그것은 범위 기반이 아닙니다 (

2
이는 STL 컨테이너에서 std::find또는 다른 과잉 기능을 사용하지 않는 한 불가능 합니다. 포함 된 요소에서 반복기를 결론을 내릴 수 없습니다. 반복자를 유지하지 않는 이유는 무엇입니까?
Eitan T

2
두 가지 이유가 있습니다. 첫 번째는 내가 원하는 것은 (이 경우) 내가 마지막 요소에 있는지 아닌지 확인하는 것입니다. 두 번째는 컴파일러가 하나를 유지해야한다는 것입니다. 왜 액세스 할 수 없습니까? "this"는 컴파일러에 의해 유지되는 범위가있는 변수입니다. 왜 여기에 있지 않습니까? 또는 자바 스크립트처럼 루프를 통과 할 때 변경되는 변수를 설정하는 대체 (하지만 여전히 편리한) 구문을 제공합니다. (목록 자동 및 인덱스)에 대한
프레드 핑클

1
@FredFinkle 당신은 실제로 맞고 반복기 가 있지만 범위 기반 for루프를 사용할 때 컴파일러 내부 이름이므로 코드에서 사용할 수 없습니다. 따라서 마지막 요소에 있는지 정말로 알고 싶다면 for(;;)루프를 사용해야합니다 .
iFreilicht 2014

답변:


62

예, 할 수 있습니다. 약간의 마사지가 필요합니다.)

비결은 컴포지션을 사용하는 것입니다. 컨테이너를 직접 반복하는 대신 인덱스를 사용하여 "압축"합니다.

전문 지퍼 코드 :

template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };

template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };


template <typename T>
class Indexer {
public:
    class iterator {
        typedef typename iterator_extractor<T>::type inner_iterator;

        typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
    public:
        typedef std::pair<size_t, inner_reference> reference;

        iterator(inner_iterator it): _pos(0), _it(it) {}

        reference operator*() const { return reference(_pos, *_it); }

        iterator& operator++() { ++_pos; ++_it; return *this; }
        iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; }

        bool operator==(iterator const& it) const { return _it == it._it; }
        bool operator!=(iterator const& it) const { return !(*this == it); }

    private:
        size_t _pos;
        inner_iterator _it;
    };

    Indexer(T& t): _container(t) {}

    iterator begin() const { return iterator(_container.begin()); }
    iterator end() const { return iterator(_container.end()); }

private:
    T& _container;
}; // class Indexer

template <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }

그리고 그것을 사용 :

#include <iostream>
#include <iterator>
#include <limits>
#include <vector>

// Zipper code here

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

    for (auto p: index(v)) {
        std::cout << p.first << ": " << p.second << "\n";
    }
}

ideone 에서 볼 수 있지만 for-range 루프 지원이 없기 때문에 덜 예쁘다.

편집하다:

Boost.Range를 더 자주 확인해야한다는 것을 기억했습니다. 불행히도 zip범위는 없지만 perl : boost::adaptors::indexed. 그러나 인덱스를 가져 오려면 반복기에 대한 액세스가 필요합니다. 수치심 : x

그렇지 않으면 counting_range제네릭과 함께 zip흥미로운 일을 할 수 있다고 확신합니다 ...

이상적인 세계에서 나는 상상할 것입니다.

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

    for (auto tuple: zip(iota(0), v)) {
        std::cout << tuple.at<0>() << ": " << tuple.at<1>() << "\n";
    }
}

zip자동으로 참조 튜플의 범위로 뷰를 생성하고 iota(0)단순히 "false"로 범위를 만드는 것을 시작에서 0그냥 카운트 무한대 방향으로 (또는 아니라, 그 유형의 최대 ...).


1
이것은 많은 유용한 정보를 제공합니다. 감사. 나는 코드를 가지고 놀 것이다. 위에서 언급했듯이 "인덱스"코드는 언어에서 제공하고 싶은 코드입니다.
Fred Finkle 2012 년

3
방법에 대한 counting_range(나 boost::counting_iterator) + boost::zip_iterator?
ildjarn

@ildjarn : 그렇습니다.
Matthieu M.

원래 인수가 rvalue이고 / 인수 인 경우의 유형을 값 유형 Indexer으로 변경하여 rvalue 인수를 적절하게 수락하고 보유하도록 변경할 수 있습니다 ._containerstd::movestd::forward
Xeo

나는 rvalue와 함께 작동하도록 코드를 편집했습니다. 슬프게도 코드를 완전히 테스트 할 수는 없지만 작동 해야 합니다. 설명은 여기를 참조 하십시오 . 마음에 들지 않거나 오류 등을 찾으면 언제든지 롤백하십시오. :)
Xeo

30

jrok이 맞습니다 : 범위 기반 for 루프는 그러한 목적으로 설계되지 않았습니다.

그러나 귀하의 경우에는 vector요소를 연속적으로 저장 하므로 포인터 산술을 사용하여 계산할 수 있습니다 (*)

vector<int> list;
for(auto& elem:list) { 
    int i = elem;
    int pos = &elem-&list[0]; // pos contains the position in the vector 

    // also a &-operator overload proof alternative (thanks to ildjarn) :
    // int pos = addressof(elem)-addressof(list[0]); 

}

그러나 이것은 코드를 난독 화하고 더 취약하게 만들기 때문에 분명히 나쁜 습관 &입니다.

참고 : C ++ 03에서는 벡터, C ++ 11 표준에서는 배열 및 문자열에 대해 연속성이 보장됩니다.


6
네, 표준에 명시되어 있습니다. 연속성은 vectorC ++ 03 arraystringC ++ 11에서 보장됩니다 .
Nicol Bolas 2012-06-09

1
" 누군가 ... &운영자 에게 과부하가 걸리면 쉽게 깨집니다 "그게 바로 그 이유 std::addressof입니다. : -]
ildjarn

네가 옳아. 따라서 &-오버로드 방지 버전은 다음과 같습니다. int pos = addressof (elem)-addressof (list [0]); .... 매튜 M.의 반복자 래퍼 : 더 나은 방법입니다
프레데릭 Terrazzoni

연속성이 보장된다는 것을 몰랐습니다. 여기서 사용하고 싶지는 않지만 알아두면 좋습니다.
Fred Finkle 2012 년

5
std :: distance를 사용하여 위치를 파악하지 않는 이유는 무엇입니까?
Michael van der Westhuizen 2013

19

아니요, 당신은 할 수 없습니다 (적어도 노력 없이는 아닙니다). 요소의 위치가 필요한 경우 범위 기반을 사용하면 안됩니다. 가장 일반적인 경우에 대한 편의 도구 일뿐입니다. 각 요소에 대해 일부 코드를 실행합니다. 요소의 위치가 필요한 덜 일반적인 상황에서는 덜 편리한 일반 for루프 를 사용해야합니다 .


15

@Matthieu의 답변을 기반으로 언급 된 boost :: adaptors :: indexed를 사용하는 매우 우아한 솔루션이 있습니다 .

std::vector<std::string> strings{10, "Hello"};
int main(){
    strings[5] = "World";
    for(auto const& el: strings| boost::adaptors::indexed(0))
      std::cout << el.index() << ": " << el.value() << std::endl;
}

당신은 그것을 시도 할 수 있습니다

이것은 언급 된 "이상적인 세계 솔루션"과 매우 유사하게 작동하며, 구문이 꽤 좋고 간결합니다. el이 경우 의 유형 은와 유사 boost::foobar<const std::string&, int>하므로 참조를 처리하고 복사가 수행되지 않습니다. 엄청나게 효율적입니다 : https://godbolt.org/g/e4LMnJ (코드는 자신의 카운터 변수를 유지하는 것과 동일합니다.)

완전성을 위해 대안 :

size_t i = 0;
for(auto const& el: strings) {
  std::cout << i << ": " << el << std::endl;
  ++i;
}

또는 벡터의 연속 속성을 사용합니다.

for(auto const& el: strings) {
  size_t i = &el - &strings.front();
  std::cout << i << ": " << el << std::endl;
}

첫 번째는 부스트 어댑터 버전 (최적)과 동일한 코드를 생성하고 마지막은 1 명령 더 길다 : https://godbolt.org/g/nEG8f9

참고 : 마지막 요소 만 알고 싶다면 다음을 사용할 수 있습니다.

for(auto const& el: strings) {
  bool isLast = &el == &strings.back();
  std::cout << isLast << ": " << el << std::endl;
}

이것은 모든 표준 컨테이너에서 작동하지만 auto&/ auto const&사용되어야 하지만 (위와 동일) 어쨌든 권장됩니다. 입력에 따라 이것은 매우 빠를 수도 있습니다 (특히 컴파일러가 벡터의 크기를 알고있는 경우).

일반 코드의 안전한 쪽이되도록 &fooby std::addressof(foo)를 대체하십시오 .


정말 우아합니다!
Matthieu M.

나는 마지막 요소 탐지에 대한 완전성에 대해 생성 된 코드의 godbolt 비교와 함께이 개 대안을 추가하고 또한 (코멘트)에 영업 이익의 필요성을 언급
Flamefire

10

C ++ 14를 지원하는 컴파일러가있는 경우 함수 스타일로 수행 할 수 있습니다.

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

template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
    int idx = 0;
    for(auto& value : container)
        op(idx++, value);
}

int main()
{
    std::vector<std::string> sv {"hi", "there"};
    for_enum(sv, [](auto i, auto v) {
        std::cout << i << " " << v << std::endl;
    });
}

clang 3.4 및 gcc 4.9에서 작동합니다 (4.8에서는 작동하지 않음). 둘 다 설정해야합니다 -std=c++1y. C ++ 14가 필요한 이유 auto는 람다 함수 의 매개 변수 때문입니다 .


2
std::function비싼 유형 삭제를 사용합니다. template<typename T, typename Callable> void for_enum(T& container, Callable op)유형 삭제 비용을 지불 할 필요가 없도록 사용 하지 않으시겠습니까?
NathanOliver

5

놀랍도록 간단한 방법이 있습니다.

vector<int> list;
for(auto& elem:list) {
    int i = (&elem-&*(list.begin()));
}

i필요한 색인은 어디에 있습니까 ?

이것은 C ++ 벡터가 항상 연속적 이라는 사실을 이용합니다 .


4

범위 기반의 사용을 고집하고 인덱스를 알고 싶다면 아래와 같이 인덱스를 유지하는 것이 매우 간단합니다. 범위 기반 for 루프에 대한 더 깨끗하고 단순한 솔루션이 없다고 생각합니다. 하지만 정말로 표준 for (;;)를 사용하지 않는 이유는 무엇입니까? 그것은 아마도 당신의 의도와 코드를 가장 명확하게 만들 것입니다.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}

1
(idx는 "별도의 반복기를 유지하는 것"에 해당)
user66081

2

나는 당신이 색인을 알고 싶은 한 가지 이유가 요소가 시퀀스의 첫 번째 / 마지막인지 아는 것임을 귀하의 의견에서 읽었습니다. 그렇다면 할 수 있습니다.

for(auto& elem:list) {
//  loop code ...
    if(&elem == &*std::begin(list)){ ... special code for first element ... }
    if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
//  if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
//  loop code ... 
}

편집 : 예를 들어 마지막 요소에서 구분 기호를 건너 뛰는 컨테이너를 인쇄합니다. 내가 상상할 수있는 대부분의 컨테이너 (배열 포함)에서 작동합니다 (온라인 데모 http://coliru.stacked-crooked.com/a/9bdce059abd87f91 ) :

#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;

template<class Container>
void print(Container const& c){
  for(auto& x:c){
    std::cout << x; 
    if(&x != &*std::prev(std::end(c))) std::cout << ", "; // special code for last element
  }
  std::cout << std::endl;
}

int main() {
  std::vector<double> v{1.,2.,3.};
  print(v); // prints 1,2,3
  std::list<double> l{1.,2.,3.};
  print(l); // prints 1,2,3
  std::initializer_list<double> i{1.,2.,3.};
  print(i); // prints 1,2,3
  std::set<double> s{1.,2.,3.};
  print(s); // print 1,2,3
  double a[3] = {1.,2.,3.}; // works for C-arrays as well
  print(a); // print 1,2,3
}

질문의 작성자가 컨테이너에 대한 for-ranged 루프의 마지막 요소를 감지하는 맥락에서이를 묻는다는 점에 유의하십시오 (부당한 반대 투표 전). 비교 왜 아무 이유가 없다는 것을 들어 &elem&*std::prev(std::end(list))일을하거나 실용적이지 않을 것입니다. 반복자 기반이 더 적합하다는 다른 답변에 동의하지만 여전히 그렇습니다.
alfC 2014 년

int i=c.size();루프 및 테스트 전에 선언하는 것이 더 쉽습니다 if(--i==0).
Marc Glisse 2014

@MarcGlisse, int i코드는 단지 예일뿐입니다. 혼동을 피하기 위해 제거하겠습니다. size루프 전에 사용하더라도 카운터가 필요합니다.
alfC 2014

2

Tobias Widlund는 멋진 MIT 라이선스 Python 스타일 헤더 만 열거했습니다 (C ++ 17).

GitHub

블로그 게시물

사용하기 정말 좋습니다.

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}

1

다음은 단순성, 컴파일 시간 및 코드 생성 품질에서 대부분의 다른 솔루션을 능가하는 매크로 기반 솔루션입니다.

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

결과:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !

1

인덱스 변수를 루프에 로컬로 두면서 보조 함수를 작성하지 않으려면 가변 변수와 함께 람다를 사용할 수 있습니다. :

int main() {
    std::vector<char> values = {'a', 'b', 'c'};
    std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
        std::cout << i << ' ' << x << '\n';
        ++i;
    });
}

0

다음은 C ++ 20을 사용하는 매우 아름다운 솔루션입니다.

#include <array>
#include <iostream>
#include <ranges>

template<typename T>
struct EnumeratedElement {
    std::size_t index;
    T& element;
};

auto enumerate(std::ranges::range auto& range) 
    -> std::ranges::view auto 
{
    return range | std::views::transform(
        [i = std::size_t{}](auto& element) mutable {
            return EnumeratedElement{i++, element};
        }
    );
}

auto main() -> int {
    auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
    for (auto const [index, element] : enumerate(elements)) {
        std::cout << "Element " << index << ": " << element << '\n';
    }
}

여기에 사용 된 주요 기능은 C ++ 20 범위, C ++ 20 개념, C ++ 11 가변 람다, C ++ 14 람다 캡처 이니셜 라이저 및 C ++ 17 구조적 바인딩입니다. 이러한 주제에 대한 정보는 cppreference.com을 참조하십시오.

참고 element결합 구조에 실제로 참조 아닌 요소 (여기는 것이 중요하지)의 복사본이다. 이는 주변의 한정자 auto가 필드 자체가 아니라 필드가 추출되는 임시 개체에만 영향을주기 때문입니다.

생성 된 코드는 (적어도 gcc 10.2에 의해) 생성 된 코드와 동일합니다.

#include <array>
#include <iostream>
#include <ranges>

auto main() -> int {
    auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
    for (auto index = std::size_t{}; auto& element : elements) {
        std::cout << "Element " << index << ": " << element << '\n';
        index++;
    }
}

증명 : https://godbolt.org/z/a5bfxz

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.