C ++迭代对象向量并将STL算法应用于成员变量

时间:2015-12-10 10:02:25

标签: c++ stl iterator

对于自定义对象的向量进行迭代但是只访问单个成员以便应用常规STL算法的快速方法又是什么?

struct Foo
{
    std::string a;
    double b = 1.0;
};

int main()
{
    std::vector<Foo> fooVector(20);

    // iterate over all members b -- as if we were iterating over a std::vector<double>
    std::discrete_distribution<int> dist(/*??*/, /*??*/);
}

&#34; quick&#34;我的意思是

  • 没有自定义迭代器 - 或者只是非常轻量级的迭代器( - 几行代码,没有提升iterator_facade等),
  • 无需修改解除引用操作符( - 不那么快)。

2 个答案:

答案 0 :(得分:2)

std::discrete_distribution<...>的构造函数不支持任何明确的项目值方式(比如在使用之前可选地应用于转换*it结果的函数对象)。因此,我认为有三种基本方法:

  1. 使用中间std::vector<double>获取迭代器产生双精度的范围:

    std::vector<double> tmp; // reserve() as desired
    std::transform(fooVector.begin(), fooVector.end(),
                   std::back_inserter(tmp),
                   [](Foo const& f){ return f.b; });
    std::discrete_distribution<int> d(tmp.begin(), tmp.end());
    
  2. 可能可以使用Foo上的转换运算符转换为double

    class Foo {
        // ...
        operator double() const { return this->b; }
    };
    // ...
    std::discrete_distribution<int> d(fooVector.begin(), fooVector.end());
    
  3. 为迭代器创建一个包装器并使用它。它并不需要任何花哨的东西,但是将一个简单的输入迭代器放在一起仍然比较复杂:

    template <typename InIt>
    class project_iterator {
        InIt it;
    public:
        explicit project_iterator(InIt it): it(it) {}
        double operator*() const { return *this->it; }
        project_iterator& operator++() { ++this->it; return *this; }
        project_iterator  operator++(int) {
            project_iterator rc(*this);
            this->operator++();
            return *this;
        }
        bool operator==(project_iterator const& other) const {
            return this->it == other.it;
        }
        bool operator!=(project_iterator const& other) const {
            return !(*this == other);
        }
    };
    template <typename It>
    project_iterator<It> project(It it) {
        return project_iterator<It>(it);
    }
    namespace std {
        template <typename It>
        class iterator_traits<project_iterator<It> {
        public:
            typedef typename std::iterator_traits<It>::difference_type difference_type;
            typedef double value_type;
            typedef double& reference;
            typedef double* pointer;
            typedef std::input_iterator_tag iterator_category;
        }
    }
    // ...
    std::discrete_distribution<int> d(project(fooVector.begin()), project(fooVector.end());
    
  4. 显然,这些方法存在差异,但我认为还有其他任何事情都可以巧妙地完成。缺少的基本上是用序列进行投影的一般方法(我通常将它们称为property maps)。

答案 1 :(得分:1)

以下是我的评论中提到的解决方案:

struct LightIterator : public std::vector<Foo>::iterator
{
    LightIterator(std::vector<Foo>::iterator it) : std::vector<Foo>::iterator(it) {}
    double& operator*() { return std::vector<Foo>::iterator::operator*().b; }
};

您可以这样使用:

Run It Online

std::accumulate(LightIterator{fooVector.begin()},
                LightIterator{fooVector.end()},
                0.0);

编辑:@TartanLlama关于与std::vector<Foo>::iterator的实际类型相关的问题是正确的。

为了尝试使用更通用的解决方案,我建议您在std::vector<Foo>::iterator是原始指针时定义包装器迭代器类。类似的东西:

(注意我现在允许选择任意属性。稍后会详细介绍)

template <
    typename PointerType,
    typename ItemType,
    typename AttributeType
>
struct LightIterator_FromPointer : public std::iterator<std::input_iterator_tag,
                                                        std::remove_pointer_t<PointerType>>
{
    PointerType it;
    AttributeType ItemType::* pointerToAttribute;
    LightIterator_FromPointer(PointerType it_, AttributeType ItemType::* pointerToAttribute_)
    : it(it_)
    , pointerToAttribute(pointerToAttribute_)
    {}

    AttributeType& operator*() { return it->*pointerToAttribute; }
    AttributeType* operator->() { return it; }

    // input iterator boilerplate: http://en.cppreference.com/w/cpp/concept/InputIterator
    using this_t = LightIterator_FromPointer<PointerType, ItemType, AttributeType>;  // less typing...
    LightIterator_FromPointer(const this_t& other) : it(other.it) {}
    bool operator!=(const this_t& other) const { return it != other.it; }
    this_t& operator++() { ++it; return *this; }
    this_t operator++(const int) { return {it++}; }
};

虽然std::vector<Foo>::iterator实际上是一个类,但仍保留原始的“最小”光迭代器:

template <
    typename IteratorType,
    typename ItemType,
    typename AttributeType
>
struct LightIterator_FromClass : public IteratorType
{
    AttributeType ItemType::* pointerToAttribute;
    LightIterator_FromClass(IteratorType it_, AttributeType ItemType::* pointerToAttribute_)
    : IteratorType(it_)
    , pointerToAttribute(pointerToAttribute_)
    {}
    AttributeType& operator*() { return IteratorType::operator*().*pointerToAttribute; }
};

最后,为了抽象出应该在调用网站上使用的光迭代器类型的细节,你可以定义一个make_iterator()函数来处理所有事情:

template <
    typename IteratorType,
    typename ItemType,
    typename AttributeType
>
typename std::conditional<std::is_pointer<IteratorType>::value,
    LightIterator_FromPointer<IteratorType, ItemType, AttributeType>,
    LightIterator_FromClass<IteratorType, ItemType, AttributeType>
>::type
make_iterator(IteratorType it, AttributeType ItemType::* pointerToAttribute)
{
    return typename std::conditional<std::is_pointer<IteratorType>::value,
        LightIterator_FromPointer<IteratorType, ItemType, AttributeType>,
        LightIterator_FromClass<IteratorType, ItemType, AttributeType>
    >::type(it, pointerToAttribute);
}

结果是一个简单的调用语法,(红利)允许选择任何属性,而不仅仅是Foo::b

Run It Online

// light iterator from an actual iterator "class"
{
    std::vector<Foo> fooVector(20);

    double acc = std::accumulate(make_iterator(fooVector.begin(), &Foo::b),
                                 make_iterator(fooVector.end(),  &Foo::b),
                                 0.0);
    cout << acc << endl;
}

// light iterator from a "pointer" iterator
{
    std::array<Foo, 20> fooVector;

    double acc = std::accumulate(make_iterator(fooVector.begin(), &Foo::b),
                                 make_iterator(fooVector.end(), &Foo::b),
                                 0.0);
    cout << acc << endl;
}