다음 코드가 있다고 가정합니다.
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
elem
별도의 반복기를 유지하지 않고 벡터에서 의 위치를 찾을 수 있습니까 ?
다음 코드가 있다고 가정합니다.
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
elem
별도의 반복기를 유지하지 않고 벡터에서 의 위치를 찾을 수 있습니까 ?
std::find
또는 다른 과잉 기능을 사용하지 않는 한 불가능 합니다. 포함 된 요소에서 반복기를 결론을 내릴 수 없습니다. 반복자를 유지하지 않는 이유는 무엇입니까?
for
루프를 사용할 때 컴파일러 내부 이름이므로 코드에서 사용할 수 없습니다. 따라서 마지막 요소에 있는지 정말로 알고 싶다면 for(;;)
루프를 사용해야합니다 .
답변:
예, 할 수 있습니다. 약간의 마사지가 필요합니다.)
비결은 컴포지션을 사용하는 것입니다. 컨테이너를 직접 반복하는 대신 인덱스를 사용하여 "압축"합니다.
전문 지퍼 코드 :
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
그냥 카운트 무한대 방향으로 (또는 아니라, 그 유형의 최대 ...).
Indexer
으로 변경하여 rvalue 인수를 적절하게 수락하고 보유하도록 변경할 수 있습니다 ._container
std::move
std::forward
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 표준에서는 배열 및 문자열에 대해 연속성이 보장됩니다.
vector
C ++ 03 array
및 string
C ++ 11에서 보장됩니다 .
&
운영자 에게 과부하가 걸리면 쉽게 깨집니다 "그게 바로 그 이유 std::addressof
입니다. : -]
@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&
사용되어야 하지만 (위와 동일) 어쨌든 권장됩니다. 입력에 따라 이것은 매우 빠를 수도 있습니다 (특히 컴파일러가 벡터의 크기를 알고있는 경우).
일반 코드의 안전한 쪽이되도록 &foo
by std::addressof(foo)
를 대체하십시오 .
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
는 람다 함수 의 매개 변수 때문입니다 .
std::function
비싼 유형 삭제를 사용합니다. template<typename T, typename Callable> void for_enum(T& container, Callable op)
유형 삭제 비용을 지불 할 필요가 없도록 사용 하지 않으시겠습니까?
놀랍도록 간단한 방법이 있습니다.
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
i
필요한 색인은 어디에 있습니까 ?
이것은 C ++ 벡터가 항상 연속적 이라는 사실을 이용합니다 .
범위 기반의 사용을 고집하고 인덱스를 알고 싶다면 아래와 같이 인덱스를 유지하는 것이 매우 간단합니다. 범위 기반 for 루프에 대한 더 깨끗하고 단순한 솔루션이 없다고 생각합니다. 하지만 정말로 표준 for (;;)를 사용하지 않는 이유는 무엇입니까? 그것은 아마도 당신의 의도와 코드를 가장 명확하게 만들 것입니다.
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
나는 당신이 색인을 알고 싶은 한 가지 이유가 요소가 시퀀스의 첫 번째 / 마지막인지 아는 것임을 귀하의 의견에서 읽었습니다. 그렇다면 할 수 있습니다.
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
}
&elem
와 &*std::prev(std::end(list))
일을하거나 실용적이지 않을 것입니다. 반복자 기반이 더 적합하다는 다른 답변에 동의하지만 여전히 그렇습니다.
int i=c.size();
루프 및 테스트 전에 선언하는 것이 더 쉽습니다 if(--i==0)
.
int i
코드는 단지 예일뿐입니다. 혼동을 피하기 위해 제거하겠습니다. size
루프 전에 사용하더라도 카운터가 필요합니다.
다음은 단순성, 컴파일 시간 및 코드 생성 품질에서 대부분의 다른 솔루션을 능가하는 매크로 기반 솔루션입니다.
#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 !
다음은 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++;
}
}