基于范围的for,使用range_expression从std::vector返回非空项

Ranged-based for with range_expression returning non-null items from std::vector

本文关键字:std expression vector 返回 range 范围 于范围 使用 for      更新时间:2023-10-16

考虑以下示例:

class Foo {
public:
    std::vector<Item*> items = { nullptr, new Item(), new Item(), nullptr };
    // function to return all non-nullptr items as an iterator
};
int main() {
    Foo foo;
    for (Item* i : foo.functionToReturnIteratorOverAllNullItems)
        // do something
}

是否有可能在类内部创建一个函数来返回std::vector中的项也驻留在类中,但跳过nullptr项?或者其他任何与此相关的项目。我在想一些lambda函数的使用会使这个工作,但我不确定如何。

注意,我希望它在不重新创建任何其他新向量并返回它的情况下是有效的。应优先使用c++ 11

您可以使用boost::adaptors::filtered(或ranges::view::filter):

// pipe version
for (Item* i : foo.items | filtered([](Item* i){return i;})) {
    // ...
}
// function version
for (Item* i : filter(foo.items, [](Item* i){return i;})) {
    // ...
}

如果您想挑战一下,这是一个更容易自己编写的范围适配器。您只需要一个迭代器类型,它所做的事情比++operator++()的转发稍微复杂一些。


但是使用if语句可能更容易,不是吗?

for (Item* i : foo.items) {
    // either positive
    if (i) {
       // ...
    }
    // or negative
    if (!i) continue;
    // ...
}

这里有一种使用高阶函数和抽象过滤逻辑的lambda的替代方法。它不需要任何额外的依赖。

template <typename TContainer, typename TF>
auto for_nonnull_items(TContainer&& container, TF f)
{
    for(auto&& i : container)
    { 
        if(i == nullptr) continue;
        f(i);
    }
    return f;
}
std::vector<int*> example{/*...*/};
for_nonnull_items(example, [](auto ptr)
   {
       // do something with `ptr`
   });

通过在foo中调用for_nonnull_items(this->items, /*...*/),你可以获得一个更好的接口:

foo.for_nonnull_items([](auto ptr)
   {
       // do something with `ptr`
   });

std::iterator派生使得自定义迭代器相当简单。在本例中,我实现了一个forward_only迭代器。如果您认为合适,可以添加更多功能。

#include <iterator>
template<class Iter>
struct non_null_forward_iterator : std::iterator<std::forward_iterator_tag, typename Iter::value_type>
{
    using value_type = typename Iter::value_type;
    non_null_forward_iterator(Iter i, Iter last) : iter_(i), last_(last)
    {
        seek();
    }
    value_type operator*() const {
        return *iter_;
    }
    non_null_forward_iterator& operator++() {
        ++iter_;
        seek();
        return *this;
    }
    void seek()
    {
        while (iter_ != last_) {
            if (*iter_)
                break;
            ++iter_;
        }
    }

    bool operator==(const non_null_forward_iterator& r) const {
        return iter_ != r.iter_;
    }
    bool operator!=(const non_null_forward_iterator& r) const {
        return iter_ != r.iter_;
    }
    Iter iter_;
    Iter last_;
};
template<class Container>
auto non_null_range(const Container& cont)
{
    using underlying_iter_type = typename Container::const_iterator;
    using iter_type = non_null_forward_iterator<underlying_iter_type>;
    struct X {
        iter_type begin() const { return begin_; }
        iter_type end() const { return end_; }
        iter_type begin_;
        iter_type end_;
    };
    return X {
        iter_type(cont.begin(), cont.end()),
        iter_type(cont.end(), cont.end())
    };
}
struct Item {};
std::ostream& operator<<(std::ostream& os, const Item& item)
{
    return std::cout << "an item";
}
int main()
{
    std::vector<Item*> items = { nullptr, new Item(), new Item(), nullptr };
    for (auto p : non_null_range(items))
    {
        std::cout << *p << std::endl;
    }
}
预期输出:

an item
an item

对另一个SO问题的回答提供了一个在这里有效的解决方案。

这在c++ 14中实现了一个通用的过滤器帮助器。

for( auto ptr: filter([](auto&& x){return x!=nullptr;})( test ) )

将遍历test的元素,使得x!=nullptr .

在c++ 11中这样做主要包括填充返回类型。例外是最后的filter函数,它返回一个lambda,这在c++ 11中是不可能的,以及在上面的lambda中使用auto&&

许多c++ 11编译器支持lambdas的auto形参。如果没有,则可以将filter返回的lambda及其参数折叠到filter函数本身中,然后写出冗长的返回值。


c++ 2x TS有协程使这变得微不足道。代码大致如下:

std::generator<Item*> non_null_items() {
  for( Item* i : items )
    if ( i ) co_return i;
}

作为类中的方法。