【问题标题】:How to parametrize on iterator direction?如何参数化迭代器方向?
【发布时间】:2011-01-20 20:36:37
【问题描述】:

基本上我在做以下事情:

std::set<int> indices;
// ..fill indices

if (flag)
{
   // we need to process in ascending order
   BOOST_FOREACH (int i, indices) 
   {
      process(i);
   }
}
else
{
   // we need to process in descending order
   BOOST_REVERSE_FOREACH (int i, indices) 
   {
      process(i);
   }
} 

我想知道是否有一种方法可以在 C++03 中编写相同的东西,只需调用一次 process(i),以某种方式对处理顺序进行参数化?像这样(显然即使在 C++0x 中也不起作用,因为 begin() 和 rbegin() 返回不同的类型):

   auto iter = flag ? indices.begin() : indices.rbegin();
   auto end =  flag ? indices.end() : indices.rend();

   BOOST_FOREACH (int i, std::make_pair(iter, end)) 
   {
      process(i);
   }

【问题讨论】:

    标签: c++ stl boost iterator foreach


    【解决方案1】:

    你想要的可以用Boost.Variant来实现。

    这个想法是定义一种新类型的迭代器,它存储一个包含正向或反向迭代器的变体(把它想象成类固醇上的 C 联合):

    template<class InputRange>
    struct any_dir_iterator
    : std::iterator_traits<typename boost::range_iterator<InputRange>::type> {
    
        typedef typename boost::range_iterator<InputRange>::type forward_iterator;
        typedef typename 
            boost::range_reverse_iterator<InputRange>::type reverse_iterator;
    
        typedef boost::variant<forward_iterator, reverse_iterator> iterator_type;
    
        iterator_type current_it, end_it;
    
        any_dir_iterator(InputRange & input_range, 
                         bool fwd = true, 
                         bool end = false) 
        {
            end_it = fwd ? iterator_type(boost::end(input_range)) 
                         : iterator_type(boost::rend(input_range));
    
            if(end)
                current_it = end_it;
            else
                current_it = fwd ? iterator_type(boost::begin(input_range)) 
                                 : iterator_type(boost::rbegin(input_range));
        }
    
        reference operator*() const {
            return boost::apply_visitor(dereference_visitor<any_dir_iterator>(), 
                                        current_it);
        }
    
        any_dir_iterator & operator++() {
            boost::apply_visitor(increment_visitor<any_dir_iterator>(), 
                                 current_it);
            return *this;
        }
    
        bool operator==(any_dir_iterator const & rhs) {
            return boost::apply_visitor(equals_visitor<any_dir_iterator>(), 
                                        current_it, rhs.current_it);
        }    
    };
    

    这类似于Adobe's any iterator,但不太通用,这意味着与普通迭代器相比,它几乎没有性能开销。

    正如您在上面的代码中看到的,所有的逻辑都委托给静态访问者,我们定义如下:

    template<class AnyDirIterator>
    struct dereference_visitor 
    : boost::static_visitor<typename AnyDirIterator::iterator_type> {
    
        typedef typename AnyDirIterator::reference result_type;
    
        template<class FwdOrRevIterator>
        result_type operator()(FwdOrRevIterator const & it) const { 
            return *it; 
        }
    };
    
    template<class AnyDirIterator>
    struct increment_visitor 
    : boost::static_visitor<typename AnyDirIterator::iterator_type> {
    
        typedef void result_type;
    
        template<class FwdOrRevIterator>
        result_type operator()(FwdOrRevIterator & it) const {
            ++it;
        }
    };
    
    template<class AnyDirIterator>
    struct equals_visitor 
    : boost::static_visitor<typename AnyDirIterator::iterator_type>
    {
        typedef bool result_type;
    
        template <typename FwdOrRevIterator>
        bool operator()(FwdOrRevIterator const & lhs, 
                        FwdOrRevIterator const & rhs) const {
            return lhs == rhs;
        }
    
        template <typename T, typename U>
        bool operator()( const T &, const U & ) const {
            return false; // comparing fwd to rev or vice-versa
        }
    };
    

    那是棘手的部分。但是我们仍然需要让它更方便使用,为此我们定义了一个辅助函数,它依赖于Boost.Range 库提供的功能:

    template<class InputRange>
    boost::iterator_range<any_dir_iterator<InputRange> > 
    make_any_dir_range(InputRange & range, bool forward=true) {
        typedef any_dir_iterator<InputRange> iterator;
        return boost::make_iterator_range(iterator(range, forward),
                                          iterator(range, forward, true));
    }
    

    仅此而已。现在你可以写了:

    int main() {
    
        int items[] = { 1, 2 };
        typedef std::vector<int> container_type;
        container_type container(items, items + sizeof(items)/sizeof(items[0]));
    
        BOOST_FOREACH(int i, make_any_dir_range(container, true))
            std::cout << i << " ";
    
        std::cout << "\n";
        BOOST_FOREACH(int i, make_any_dir_range(container, false))
            std::cout << i << " ";
    
        return 0;
    }
    

    哪些打印:

    1 2
    2 1
    

    这也适用于 const 容器,尽管我没有在 main 函数中展示这种可能性。

    使用 Boost.Range 带来的另一个好处是它适用于开箱即用的数组。所以你可以这样做:

    int items[] = { 1, 2 };
    
    BOOST_FOREACH(int i, make_any_dir_range(items, true)) // Prints "1 2"
        std::cout << i << " ";
    

    这个答案太简短了,我留下了一些未实现的东西(但它们都是样板,不需要新访问者):

    • 后缀增量运算符
    • != 运算符
    • -> 运算符

    这里是all the code in Codepad。由于“将警告视为错误”政策,键盘不会吞下它,但 VS2008 和 GCC 4.4 都可以在我的本地机器上编译它。

    更新

    我做了一些测试,显然boost::variant 确实引入了一些运行时开销:一个基于BOOST_FOREACH 的循环,如main 函数中的循环,比使用普通迭代器的等效版本。检查这是否比 Adob​​e 的 any_iterator 引入的开销最好还是最差会很有趣。

    【讨论】:

    • 哇,这比我预期的要多;)我想知道是否可以使用良好的 'ol union 解决方案。但我已经认为,有一个额外的 if 比引入这个复杂的代码更好。如果我经常做这种事情可能是值得的,但这只是在几个地方。还是谢谢!
    • @Alex - 我不认为工会可以在这里使用,请检查:stackoverflow.com/questions/943267/…
    【解决方案2】:

    那么显而易见的是创建一个类来处理这种情况的逻辑,要么通过存储标志,要么使用多态性。但是,它充其量只是“隐藏”if 语句。

    【讨论】:

    • 您能否在不粗略地使用宏的情况下勾勒出处理这种情况的逻辑?在我看来,在这个类内部仍然会有两个(间接)调用来处理两个不同的循环。
    • 您可以使用模板来实现(一个循环,两个场景)。我不明白您如何在 runtime 确定要使用的迭代器类型 - 即使使用宏 - 也无需编写两个迭代器的逻辑。
    猜你喜欢
    • 2018-10-31
    • 2015-08-12
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 2012-02-03
    • 2012-02-09
    • 2020-11-06
    • 2017-04-11
    相关资源
    最近更新 更多