【问题标题】:C++ iterate over vector of objects and apply STL algorithms to member variablesC++ 迭代对象向量并将 STL 算法应用于成员变量
【发布时间】:2016-03-15 21:46:20
【问题描述】:

还有什么方法可以快速迭代自定义对象向量但只访问一个成员以应用通用 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(/*??*/, /*??*/);
}

我的意思是“快速”

  • 没有自定义迭代器——或者只有一个非常轻量级的迭代器(——几行代码,没有提升iterator_facade等),
  • 不修改解引用运算符(-- 没那么快)。

【问题讨论】:

  • 我不确定我是否理解您为什么不想使用自定义二进制操作。您特别想使用哪些算法不允许提供要使用的函数?
  • @TartanLlama:std::discrete_distribution 构造函数采用迭代器......认为这太分散注意力了,原因未知
  • std::discrete_distribution&lt;double&gt;吗?
  • @Caduchon:不,int 指定了返回类型。问题是构造函数在取消引用时不采用返回 Foo const&amp; 的迭代器。它需要像 double 这样的标量类型。

标签: c++ stl iterator


【解决方案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&lt;Foo&gt;::iterator 的实际类型相关的问题是正确的。

作为一种更通用的解决方案的尝试,我建议您为 std::vector&lt;Foo&gt;::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&lt;Foo&gt;::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);
}

结果是一个简单的调用语法,(bonus) 允许选择任何属性,而不仅仅是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;
}

【讨论】:

  • 我不认为这是便携的。 std::vector&lt;Foo&gt;::iterator 可能只是一个指针,因此从它继承将是一个编译时错误。您最好将迭代器作为成员并将所有必要的运算符转发给内部迭代器。
  • @TartanLlama 你是对的! :) 我刚刚修改了代码以考虑到这一点。
【解决方案2】:

std::discrete_distribution&lt;...&gt; 的构造函数不支持任何显式方式来投影值(例如在使用之前可选地应用于转换 *it 的结果的函数对象)。因此,我认为有三种基本方法:

  1. 使用中间std::vector&lt;double&gt; 来获得迭代器产生双倍的范围:

    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 转换为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());
    

显然,这些方法存在差异,但我认为没有其他方法可以巧妙地完成。缺少的本质上是一种对序列进行投影的通用方法(我通常将它们称为property maps)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-26
    • 2020-11-06
    • 1970-01-01
    • 2020-03-05
    • 1970-01-01
    • 2012-01-05
    相关资源
    最近更新 更多