【问题标题】:How to slice with for-range loop ? C++0x如何使用 for-range 循环切片? C++0x
【发布时间】:2010-02-03 22:54:37
【问题描述】:

在 C++0X 中使用基于范围的 for 循环,我知道我们可以做到:

std::vector<int> numbers = generateNumbers();

for( int k : numbers )
{
   processNumber( k );
}

(用 lambda 写起来可能更简单)

但是如果我只想将 processNumber(k) 应用于数字的一部分,我该怎么办?例如,我应该如何编写这个 for 循环以将 processNumber() 应用于数字的一半(头部或尾部)?在 Python 或 Ruby 中是否允许“切片”?

【问题讨论】:

  • std::for_each(from, to, [] (int k) { processNumber(k); }); 可能会更容易。或者您必须为该向量中的每个兼容子范围提供一个。
  • 是的,我知道这一点。我只想知道 C++ 中 for-range 循环与切片“容易”的其他语言相比的局限性。
  • 上面显示的for_each解决方案不是“简单”吗?
  • @Nick,这假设 numbers 是数字 from..to 而(据我理解的问题)它们可能是任何数字,您只想处理其中一些(例如 s/generateNumbers()/firstPrimes(100)/ )

标签: c++ c++11


【解决方案1】:

您可以使用Boost.Range 库中的“slicedrange adaptor

#include <boost/range/adaptor/sliced.hpp>

using boost::adaptors::sliced;

...

std::vector<int> numbers = generateNumbers();
for( int k : numbers | sliced(0, numbers.size() / 2))
{
     processNumber( k );
}   

【讨论】:

    【解决方案2】:

    一种可能是boost的iterator_range

    (没有支持基于范围的编译器,而是使用BOOST_FOREACH。我希望基于范围的工作相同,只要容器或范围具有开始和结束方法。)

    #include <boost/foreach.hpp>
    #include <boost/range/iterator_range.hpp>
    #include <iostream>
    #include <vector>
    
    int main()
    {
        std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        BOOST_FOREACH(int n, boost::make_iterator_range(v.begin(), v.begin() + v.size() / 2)) {
            std::cout << n << '\n';
        }
    }
    

    为方便起见,您还可以创建自己的切片函数,因此它可以接受索引而不是迭代器。同样,它可以基于 boost.iterator_range,也可以不:

    #include <cstddef>
    #include <iterator>
    
    template <class Iterator>
    class iter_pair
    {
    public:
        typedef Iterator iterator;
        typedef Iterator const_iterator; //BOOST_FOREACH appears to want this
        iter_pair(iterator first, iterator last): first(first), last(last) {}
        iterator begin() const { return first; }
        iterator end() const { return last; }
    private:
        iterator first, last;
    };
    
    template <class Container>
    struct iterator_type
    {
        typedef typename Container::iterator type;
    };
    
    template <class Container>
    struct iterator_type<const Container>
    {
        typedef typename Container::const_iterator type;
    };
    
    template <class Container>
    iter_pair<typename iterator_type<Container>::type>
        slice(Container& c, size_t i_first, size_t i_last)
    {
        typedef typename iterator_type<Container>::type iterator;
        iterator first = c.begin();        
        std::advance(first, i_first);
        iterator last = first;
        std::advance(last, i_last - i_first);
        return iter_pair<iterator>(first, last);
    }
    
    template <class Container>
    iter_pair<typename iterator_type<Container>::type>
        slice(Container& c, size_t i_last)
    {
        return slice(c, 0, i_last);
    }
    
    //could probably also be overloaded for arrays
    
    #include <cctype>
    #include <string>
    #include <boost/foreach.hpp>
    #include <iostream>
    
    int main()
    {
        std::string s("Hello world, la-la-la!");
        BOOST_FOREACH( char& c, slice(s, 2, 11)) {
            if (c == 'l')
                c = std::toupper(c);
        }
        const std::string& r = s;
        BOOST_FOREACH( char c, slice(r, r.size() - 1) ) {
            std::cout << c << " ";
        }
        std::cout << '\n';
    }
    

    一般来说,首先可能会使用迭代器,所以它可能没那么有用。

    【讨论】:

    • +1 很好的解决方案,如果您习惯使用范围(而不是使用迭代器),这绝对是您的最佳选择。我建议的一个优化是放置“迭代器最后=第一;”行在推进“第一”的线下方。当然,这意味着“last”必须提前“i_last-i_first”,而不是“i_last”。
    • 是的,它很棒,但我正在寻找的是一种基于 for-range 循环的方法。我觉得如果没有切片,for-range 循环可能是“不完整的”,但也许我只是不知道如何使用这种新语法进行切片?
    • @Klaim:它不适用于 range-for 吗?我无法测试,也不知道有任何新的特殊切片语法。据我所知,任何提供 begin() 和 end() 方法返回类似迭代器的东西都可以。 @Manuel:谢谢,编辑代码。
    【解决方案3】:

    这样的事情可能会起作用(未选中,因为我无权访问 C++0x 编译器)

    编辑: 在 VS10 上检查过,当然我必须修复许多错误....

    定义一个类,它是任何容器的代理,其iterators 只返回容器的一个子集。我提供的示例是前半部分最简单的示例,但可以更通用。

    template <class Container>
    class head_t {
        Container& c_;
    public:
        template <class T>
        class iter {
            T curr_;
            const T& end_;
            int limit_; // count how many items iterated
        public:
            iter(T curr, const T& end) 
                : curr_(curr)
                , end_(end)             
                , limit_(std::distance(curr_, end_)/2)
                { }
    
            typename Container::value_type operator*() { return *curr_; }
    
            // Do the equivilant for for operator++(int)
            iter& operator++() {
                if (--limit_ == 0) // finished our slice
                    curr_ = end_; 
                else
                    ++curr_;
                return *this;
            }
    
            bool operator!=(const iter& i) const {
                return curr_ != i.curr_; 
            }
        };
    
        head_t(Container& c) : c_(c) {}
        iter<typename Container::iterator> begin() { 
            return iter<typename Container::iterator>(c_.begin(), c_.end()); 
        }
    
        iter<typename Container::iterator> end() {
            return iter<typename Container::iterator>(c_.end(), c_.end()); 
        }    
    };
    
    template <class T>
    head_t<T> head(T& t) { return head_t<T>(t); }
    

    然后你在循环中使用它:

    for( int k : head(numbers) )
    

    【讨论】:

    • 好答案。请注意,以_t 结尾的名称是保留的:stackoverflow.com/questions/228783/…
    • +1。只是一些建议:您应该从 std::iterator_traits 派生 iter 类。但是无论容器如何,您都希望它成为输入迭代器,因此您还应该将“typedef std::input_iterator_tag iterator_category”添加到类主体中。另外,你忘了实现 operator==。
    • 要实现使用for_each oneliner 可以做的事情,需要做很多工作。
    • @Manuel iteratorvector 的某些实现中可能是 T*,所以这不起作用...
    • @jalf,你能举个例子吗?
    猜你喜欢
    • 2017-01-25
    • 2016-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 2021-06-16
    • 2019-12-21
    相关资源
    最近更新 更多