c++ - 与 get、tie 和其他元组操作一起使用的元组包装器

标签 c++ iterator tuples c++17 stdtuple

我写了一个花哨的“zip 迭代器”,它已经完成了许多角色(可以用于 for_each、复制循环、容器迭代器范围构造函数等......)。
在处理所涉及的对/元组的所有模板代码下,归结为迭代器的解引用运算符返回一个元组/一对引用,而不是对元组/对的引用。
我希望我的迭代器与 std::sort 一起工作,所以我需要能够做 swap(*iter1, *iter2)并在迭代的原始容器中切换基础值。
代码和一个小demo可以看这里(有点看不懂):http://coliru.stacked-crooked.com/a/4fe23b4458d2e692
尽管 libstdc++ 的排序使用 std::iter_swap其中调用 swap ,例如libc++ 没有,它只是调用 swap直接,所以我想要一个涉及 swap 的解决方案作为定制点。
我已经尝试过(并且非常接近工作)而不是返回 std::pair/std::tuple来自 operator*正如我现在所做的,返回的是一个简单的包装器类型。目的是让包装器表现得好像它是 std::pair/std::tuple ,并允许我写一个 swap为它发挥作用。
它看起来像这样:

template<typename... ValueTypes>
struct TupleWrapper : public PairOrTuple_t<ValueTypes...>
{
    using PairOrTuple_t<ValueTypes...>::operator=;
    template<typename... TupleValueTypes>
    operator PairOrTuple_t<TupleValueTypes...>() const
    {
       return static_cast<PairOrTuple_t<ValueTypes...>>(*this);
    }
};

template<std::size_t Index, typename... ValueTypes>
decltype(auto) get(TupleWrapper<ValueTypes...>& tupleWrapper)
{
    return std::get<Index>(tupleWrapper);
}
template<std::size_t Index, typename... ValueTypes>
decltype(auto) get(TupleWrapper<ValueTypes...>&& tupleWrapper)
{
    return std::get<Index>(std::forward<TupleWrapper<ValueTypes...>>(tupleWrapper));
}
template<typename... ValueTypes,
         std::size_t... Indices>
void swap(TupleWrapper<ValueTypes...> left,
          TupleWrapper<ValueTypes...> right,
          const std::index_sequence<Indices...>&)
{
    (std::swap(std::get<Indices>(left), std::get<Indices>(right)), ...);
}

template<typename... ValueTypes>
void swap(TupleWrapper<ValueTypes...> left,
          TupleWrapper<ValueTypes...> right)
{
    swap(left, right, std::make_index_sequence<sizeof...(ValueTypes)>());
}


namespace std
{
    template<typename... ValueTypes>
    class tuple_size<utility::implementation::TupleWrapper<ValueTypes...>> : public tuple_size<utility::implementation::PairOrTuple_t<ValueTypes...>> {};
    template<std::size_t Index, typename... ValueTypes>
    class tuple_element<Index, utility::implementation::TupleWrapper<ValueTypes...>> : public tuple_element<Index, utility::implementation::PairOrTuple_t<ValueTypes...>> {};
}
完整代码在这里:http://coliru.stacked-crooked.com/a/951cd639d95af130 .
operator* 中返回此包装器似乎可以编译(至少在 GCC 上)但会产生垃圾。
在 Clang 的 libc++ 上,std::tie无法编译。
两个问题:
  • 我怎样才能用 libc++ 编译它(魔术似乎在于 TupleWrapper 的转换运算符?)
  • 为什么结果是错误的,我做错了什么?

  • 我知道它有很多代码,但是我不能把它写得更短,因为所有交换元组包装器的小例子对我来说都很好。

    最佳答案

    第一个问题
    问题之一是ZipIterator类不满足 RandomAccessIterator 的要求.

  • std::sort需要 RandomAccessIterator s 作为其参数
  • RandomAccessIterator s 必须是 BidirectionalIterator s
  • BidirectionalIterator s 必须是 ForwardIterator s

  • ForwardIterator s 的条件是 ::reference 必须是 value_type&/const value_type& :

    The type std::iterator_traits<It>::reference must be exactly

    • T& if It satisfies OutputIterator (It is mutable)
    • const T& otherwise (It is constant)

    (where T is the type denoted by std::iterator_traits<It>::value_type)


    其中ZipIterator目前没有实现。
    它适用于 std::for_each和类似的函数,只需要迭代器满足 InputIterator 的要求/ OutputIterator .

    The reference type for an input iterator that is not also a LegacyForwardIterator does not have to be a reference type: dereferencing an input iterator may return a proxy object or value_type itself by value (as in the case of std::istreambuf_iterator).


    tl;博士: ZipIterator可以用作 InputIterator/OutputIterator ,但不是 ForwardIterator , 其中 std::sort需要。
    第二个问题
    @T.C.their comment 中指出std::sort允许将值移出容器,然后再将它们移回。

    The type of dereferenced RandomIt must meet the requirements of MoveAssignable and MoveConstructible.


    其中ZipIterator当前无法处理(它从不复制/移动引用的对象),所以这样的事情不能按预期工作:
    std::vector<std::string> vector_of_strings{"one", "two", "three", "four"};
    std::vector<int> vector_of_ints{1, 2, 3, 4};
        
            
    auto first = zipBegin(vector_of_strings, vector_of_ints);
    auto second = first + 1; 
        
    // swap two values via a temporary
    auto temp = std::move(*first);
    *first = std::move(*second);
    *second = std::move(temp);
    
    // Result:
    /*
      two, 2
      two, 2
      three, 3
      four, 4
    */
    
    ( test on Godbolt )
    结果
    不幸的是,不可能创建一个迭代器来动态生成元素并且可以用作 ForwardIterator使用当前标准(例如 this question )
  • 你当然可以编写自己的算法,只需要 InputIterator s/OutputIterator s(或以不同方式处理您的 ZipIterator)
    例如一个简单的冒泡排序:( Godbolt )
  • template<class It>
    void bubble_sort(It begin, It end) {
        using std::swap;
    
        int n = std::distance(begin, end);
    
        for (int i = 0; i < n-1; i++) {   
            for (int j = 0; j < n-i-1; j++) {
            if (*(begin+j) > *(begin+j+1)) 
                swap(*(begin+j), *(begin+j+1)); 
            }
        }
    }
    
  • 或者换个ZipIterator类满足RandomAccessIterator .
    不幸的是,如果不将元组放入像数组这样的动态分配结构中(您可能试图避免这种结构),我想不出一种可能的方法
  • 关于c++ - 与 get、tie 和其他元组操作一起使用的元组包装器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63137322/

    相关文章:

    c++ - 为什么编译器不能像在变量声明中那样在结构成员中设置 char[] = { .. } 的大小?

    C++ 非 QT 接口(interface)和 QtNetwork

    c++ - 由于 ID3D11Buffer 而导致访问冲突

    c++ - 准备废弃 std::iterator

    c++ - vector 迭代器不可取消引用(尝试手动反转 vector )

    postgresql - 如何在 PostgreSQL 中存储多个元组数组

    algorithm - 在 Haskell 中避免重复

    c++ - 无法使用 const char * 数组实例化模板

    c++ - 比较不同大小的元组

    c++ - 无法在另一个类的迭代器类中使operator <<