基于范围的专用地图值循环

时间:2018-07-05 13:58:24

标签: c++ c++11 for-loop generator

我有以下代码:

#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>

class MyObject
{
public:
    MyObject()
        : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
    {}

    RETURNTYPE GetStringIterator() const
    {
        IMPLEMENTATION
    }

private:
    std::map<int, std::string> m_Items;
};


int main()
{
    MyObject o;
    for (auto& s : o.GetStringIterator())
    {
        std::cout << s;
    }
}

RETURNTYPEIMPLEMENTATION应该是什么,以便允许MyObject的任何客户端(在本例中为main()函数)迭代{ {1}}地图,而不复制任何数据?似乎这对于基于c ++ 11 range的循环和迭代器应该是可能的。但是我还不知道怎么做。

3 个答案:

答案 0 :(得分:8)

基于范围的迭代可以像这样实现:

class MyObject
{
public:
    MyObject()
        : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
    {}

    auto begin()       { return m_Items.begin(); }
    auto begin() const { return m_Items.begin(); }
    auto end()       { return m_Items.end(); }
    auto end() const { return m_Items.end(); }

private:
    std::map<int, std::string> m_Items;
};

复制或不复制值取决于在呼叫站点编写代码的方式:

MyObject a;
for(auto [key,value] : a) {} // copies are made
for(auto & [key,value] : a) {} // no copy
for(auto const & [key,value] : a) {} // no copy

并且您可以通过删除beginend的非常量版本来禁用对映射值的修改:

class MyObject
{
public:
    MyObject()
        : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
    {}

    auto begin() const { return m_Items.begin(); }
    auto end() const { return m_Items.end(); }

private:
    std::map<int, std::string> m_Items;
};

然后,尝试在range-for循环中修改该值将导致编译错误:

MyObject a;
for(auto & [key,value] : a) {
    //value.push_back('a');      // Not OK 
}
for(auto & [key,value] : a) {
    cout << value;             // OK
}

请注意,如果地图是实现的详细信息,则应使用@Barry提出的答案,因为它仅在地图的值上进行迭代,而不在键上进行迭代。

答案 1 :(得分:4)

您可以使用boost::adaptors::map_values,它可以在C ++ 11中使用:

auto GetStringIterator() const
    // NB: have the move the declaration of m_Items ahead of this function for this to work
    -> decltype(m_Items | boost::adaptors::map_values)
{
    return m_Items | boost::adaptors::map_values;
}

或其等效于range-v3的view::values。如果您愿意的话,两者都可以像values(m)而不是m | values一样使用。

这两种解决方案都会在地图的值上返回一个 view 。这是一个不拥有任何底层元素的对象,并且复制便宜(即O(1))。我们不会改变地图或其任何底层元素。

您可以将其用作其他范围:

for (std::string const& s : o.GetStringIterator()) {
    // ...
}

此循环不复制任何字符串。每个s直接指向string存储的相应map

答案 2 :(得分:3)

我将首先在中回答这个问题。

这是最小的映射迭代器:

template<class F, class It>
struct iterator_mapped {
  decltype(auto) operator*() const {
    return f(*it);
  }

  iterator_mapped( F f_in, It it_in ):
    f(std::move(f_in)),
    it(std::move(it_in))
  {}

  iterator_mapped( iterator_mapped const& ) = default;
  iterator_mapped( iterator_mapped && ) = default;
  iterator_mapped& operator=( iterator_mapped const& ) = default;
  iterator_mapped& operator=( iterator_mapped && ) = default;

  iterator_mapped& operator++() {
    ++it;
    return *this;
  }
  iterator_mapped operator++(int) {
    auto copy = *this;
    ++*this;
    return copy;
  }
  friend bool operator==( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
    return lhs.it == rhs.it;
  }
  friend bool operator!=( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
    return !(lhs==rhs);
  }
private:
  F f;
  It it;
};

从技术上讲,它不是迭代器,但符合for(:)循环的条件。

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
};
template<class It>
range_t<It> range( It b, It e ) {
  return {std::move(b), std::move(e)};
}

上面是绝对最小的迭代器范围类型,可以for(:)进行迭代。

template<class F, class R>
auto map_range( F&& f, R& r ) {
  using std::begin; using std::end;
  auto b = begin(r);
  auto e = end(r);
  using it = iterator_mapped<std::decay_t<F>, decltype(b)>;
  return range( it( f, b ), it( f, e ) );
}

请注意,R&不是R&&;在这里为r取右值是危险的。

auto GetStringIterator() const
{
  return map_range( [](auto&& pair)->decltype(auto){
    return pair.second;
  }, m_Items );
}

完成。

将其转换为很痛苦。您必须扔掉std::function来代替lambda(或编写执行任务而不是lambda的函数对象),将decltype(auto)替换为auto并尾随返回类型,给出确切的值auto&&到lambdas等参数的类型,等等。最终,您将获得大约25%-50%的代码,其中大部分使类型的追赶变得模糊。

这基本上是boost::adaptors::map_values的工作,但是它是手动滚动的,因此您可以了解它的工作原理,并且没有增强依赖。