在C++ 11基于范围的for循环中查找元素的位置?

Fre*_*kle 71 c++ iterator c++11

假设我有以下代码:

vector<int> list;
for(auto& elem:list) {
    int i = elem;
}
Run Code Online (Sandbox Code Playgroud)

我可以在elem不保持单独迭代器的情况下找到向量中的位置吗?

Mat*_* M. 61

是的,你可以,只需要一些按摩;)

诀窍是使用组合:不是直接迭代容器,而是沿着路径"压缩"它.

专业拉链码:

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); }
Run Code Online (Sandbox Code Playgroud)

并使用它:

#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";
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以在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";
    }
}
Run Code Online (Sandbox Code Playgroud)

通过zip自动创建视图作为一系列参考元组,并iota(0)简单地创建一个"假"范围,该范围从0无穷大开始并且仅计入无穷大(或者,其类型的最大值......).

  • 怎么样`counting_range`(或[`boost :: counting_iterator`](http://www.boost.org/libs/iterator/doc/counting_iterator.html))+ [`boost :: zip_iterator`](http:/ /www.boost.org/libs/iterator/doc/zip_iterator.html)? (3认同)

Fré*_*oni 27

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]); 

}
Run Code Online (Sandbox Code Playgroud)

但这显然是一种不好的做法,因为它会混淆代码并使其更加脆弱(如果有人更改了容器类型,&操作员过载或者将'auto&'替换为'auto',它很容易中断.祝好运调试!)

注意:C++ 03中的向量以及C++ 11标准中的数组和字符串保证了连续性.

  • 是的,它在标准中说明.C++ 03中的`vector`和C++ 11中的`array`和`string`保证了连续性. (6认同)
  • 为什么不使用std :: distance来确定位置? (5认同)
  • “*如果有人……重载 `&amp;` 运算符,它很容易崩溃*” 这就是 `std::addressof` 的用途。:-] (2认同)

Nic*_*las 18

不,你不能(至少,不是没有努力).如果您需要元素的位置,则不应使用基于范围的.请记住,它只是最常见情况下的便利工具:为每个元素执行一些代码.在需要元素位置的不太常见的情况下,您必须使用不太方便的常规for循环.


Fla*_*ire 11

基于@Matthieu的答案,使用提到的boost :: adapters :: 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;
}
Run Code Online (Sandbox Code Playgroud)

你可以尝试一下

这非常类似于提到的"理想世界解决方案",具有漂亮的语法和简洁.请注意,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;
}
Run Code Online (Sandbox Code Playgroud)

或者使用向量的连续属性:

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

第一个生成与boost适配器版本(最佳)相同的代码,最后一个生成1个指令:https://godbolt.org/g/nEG8f9

注意:如果您只想知道,如果您有最后一个元素可以使用:

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

这适用于每个标准容器但auto&/ auto const&必须使用(同上),但无论如何推荐.根据输入,这可能也非常快(特别是当编译器知道向量的大小时)

&fooby 替换为std::addressof(foo)安全方面的通用代码.


Mic*_*ael 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;
    });
}
Run Code Online (Sandbox Code Playgroud)

使用clang 3.4和gcc 4.9(不是4.8); 两者都需要设定-std=c++1y.你需要c ++ 14的原因是因为autolambda函数中的参数.

  • `std::function` 使用类型擦除,这是昂贵的。为什么不使用`template&lt;typename T, typename Callable&gt; void for_enum(T&amp; container, Callable op)` 这样您就不必为类型擦除付费? (2认同)

Bjö*_*din 7

这是一个使用 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';
    }
}
Run Code Online (Sandbox Code Playgroud)

这里使用的主要功能是 c++20 范围、c++20 概念、c++11 可变 lambda、c++14 lambda 捕获初始值设定项和 c++17 结构化绑定。有关这些主题的信息,请参阅 cppreference.com。

请注意,element结构化绑定实际上是一个引用,而不是元素的副本(这里并不重要)。这是因为 the 周围的任何限定符仅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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

证明: https: //godbolt.org/z/a5bfxz

  • 这是你习惯的问题。这对我来说更容易理解,因为这是我去年编写的代码。我选择使用哪些功能以及何时使用纯粹是基于对安全性和实用性的推理。对我来说,这就像学习一门新语言,具有更好的性能、安全性和简单性(抽象)的潜力。 (4认同)

Jen*_*low 6

如果您坚持使用基于范围的范围并了解索引,那么维护索引非常简单,如下所示。我认为基于范围的 for 循环没有更清洁/更简单的解决方案。但真的为什么不使用标准 for(;;) 呢?这可能会使您的意图和代码最清晰。

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


Pul*_*Jet 5

有一种非常简单的方法可以做到这一点

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

i你需要的索引在哪里。

这利用了C++ 向量始终是连续的这一事实。