【问题标题】:boost::transform_iterator and std::iter_swapboost::transform_iterator 和 std::iter_swap
【发布时间】:2019-01-31 22:49:00
【问题描述】:

我正在尝试概括一个函数,该函数用于将两个迭代器用于特定数据结构的向量,并使用std::iter_swap 以某种方式重新排列元素(就像std::sort 一样)。

由于这个函数实际上只需要数据的一个子集,而且我以后需要在其他上下文中使用它,所以我考虑去除对数据结构的依赖,并在调用时使用boost::transform_iterator处理转换。

不幸的是,boost::transform_iterator 似乎对这种变化并不满意。我可以想象为什么:std::iter_swap 通常实现为std::swap(*lhs, *rhs),而取消引用transform_iterator 不会产生以正确方式交换的原始元素。

我想知道是否有办法处理这种情况。如果需要,我愿意使用boost::range 或实验性的std::ranges ts。

这个问题可能类似于this one,但即便如此,解决方案最终也会修改算法所需的数据子集,而不是外部结构。

这是一个 MWE:

#include <boost/iterator/transform_iterator.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

struct A {
    int x;
    int y;
};

template <typename It>
void my_invert(It begin, It end) {
    while (begin < end) {
        std::iter_swap(begin++, --end);
    }
}

template <typename It>
void my_print(It begin, It end) {
    for (; begin != end; ++begin)
        std::cout << (*begin) << ' ';
    std::cout << '\n';
}

int main() {
    std::vector<int> x{7,6,5,4,3,2};

    my_invert(std::begin(x), std::end(x));
    my_print(std::begin(x), std::end(x));

    auto unwrap = +[](const A & a) { return a.x; };

    std::vector<A> y{{9,8}, {7,6}, {5,4}, {3,2}};

    auto begin = boost::make_transform_iterator(std::begin(y), unwrap);
    auto end = boost::make_transform_iterator(std::end(y), unwrap);

    //my_invert(begin, end); // Does not work.
    my_print(begin, end);

    return 0;
}

【问题讨论】:

    标签: c++ boost iterator c++17


    【解决方案1】:

    访问底层迭代器

    您可以访问transform_iteratorbase() 属性(从iterator_adaptor 公开继承)来实现您的自定义transform_iter_swap,用于交换包装迭代器的基础数据。

    例如:

    template<class IteratorAdaptor>
    void transform_iter_swap(IteratorAdaptor a, IteratorAdaptor b)
    {
       std::swap(*a.base(), *b.base());
    }
    
    template <typename It>
    void my_invert(It begin, It end) {
        while (begin < end) {
            transform_iter_swap(begin++, --end);
        }
    }
    

    之后您的示例(省略 std::vector 部分)按预期运行:

    my_invert(begin, end); // OK
    my_print(begin, end);  // 3 5 7 9
    

    如果你想要一个通用的函数模板来涵盖 boost(适配器)迭代器以及典型的迭代器,你可以例如根据迭代器 public typedef iterator_category 是否派生自 boost::iterators::no_traversal_tag 使用 if constexpr (C++17):

    // expand includes with
    #include <boost/iterator/iterator_categories.hpp>
    
    template <class It>
    void iter_swap(It a, It b) {
      if constexpr(std::is_base_of<
            boost::iterators::no_traversal_tag,
            typename It::iterator_category>::value) {
          std::swap(*a.base(), *b.base());
        }
      else {
        std::swap(*a, *b);
      }
    }
    
    template <typename It>
    void my_invert(It begin, It end) {
        while (begin < end) {
            iter_swap(begin++, --end);
        }
    }
    

    【讨论】:

    • 这是有道理的,我确实考虑过这一点。我看到的唯一问题是这基本上迫使我在my_invert 中使用变换迭代器,即使我不需要它们(例如对于第一个向量)。我可以将其作为一个单独的实现(类似于my_invert_transformed),但如果有办法保持该方法的通用性就好了。
    • 您可以利用 SFINAE 在“真正的迭代器”和例如特殊的(例如提升transform_iterator):我可以添加一个示例。
    • 也许我可以使用if constexpr 自动调用base()(如果存在),但它看起来有点像黑客。
    • 我正在研究的另一个想法是将 std::iter_swap 专门用于 boost 迭代器,以便它们做正确的事情..
    • @Svalorzen 我自己没有使用过boost:range,但也许这是一个选择。我用一种 (C++17) 方法扩展了我的答案,以判断何时根据 base() 进行交换。
    【解决方案2】:

    问题来自您通过的一元谓词。请注意,由于您允许推导返回类型,因此返回类型被推导为 int,返回一个副本,并且当您尝试交换两个不可修改的 int 时编译失败。但是,如果您要将返回类型指定为 int&,如下所示:

     auto unwrap = [](A & a)->int& { return a.x; }; // explicitly demand to return ref
    

    它将编译和反转元素。在 gcc 8.1.0 和 clang 6.0.0 上测试。

    【讨论】:

    • 谢谢,这确实可能是问题之一。但是请注意,在您的解决方案中,只有 a.x 会被还原。我想要的是原始元素被还原。如,{9, 8} 应该放在最后,但不能更改为 {3,8}
    • 好吧,我怀疑你能不能让它工作,老实说看不出你想让它工作的原因——你仍然可以只使用向量的普通迭代器来反转元素,例如仅使用 transform_iterator 打印。
    • 我发布的代码是一个例子。我没有打印数据。我正在对它们进行复杂的转换,并且交换取决于我提供给函数的数据。因此不,我不能使用普通的迭代器来反转元素。
    猜你喜欢
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    • 2012-05-31
    • 2012-08-15
    • 1970-01-01
    • 2016-07-07
    • 2019-12-26
    • 2012-06-16
    相关资源
    最近更新 更多