我的目标是要运行以下代码:

#include<iostream>
#include<vector>
#include<list>
#include<algorithm>
#include<array>
#include"functional.hpp"

int main( )
{
    std::vector<double> a{1.0, 2.0, 3.0, 4.0};
    std::list<char> b;
    b.push_back('a');
    b.push_back('b');
    b.push_back('c');
    b.push_back('d');
    std::array<int,5> c{5,4,3,2,1};

    auto d = zip(a, b, c);

    for (auto i : zip(a, b, c) )
    {
        std::cout << std::get<0>(i) << ", " << std::get<1>(i) << ", " << std::get<2>(i) << std::endl;
    }

    for (auto i : d)
    {
        std::cout << std::get<0>(i) << ", " << std::get<1>(i) << ", " << std::get<2>(i) << std::endl;
        std::get<0>(i) = 5.0;
        //std::cout << i1 << ", " << i2 << ", " << i3 << std::endl;
    }
    for (auto i : d)
    {
        std::cout << std::get<0>(i) << ", " << std::get<1>(i) << ", " << std::get<2>(i) << std::endl;
        //std::cout << i1 << ", " << i2 << ", " << i3 << std::endl;
    }
}


输出:

1, a, 5
2, b, 4
3, c, 3
4, d, 2
5, a, 5
5, b, 4
5, c, 3
5, d, 2


“ functional.hpp”的来源是:

#pragma once
#include<tuple>
#include<iterator>
#include<utility>

/***************************
// helper for tuple_subset and tuple_tail (from http://stackoverflow.com/questions/8569567/get-part-of-stdtuple)
***************************/
template <size_t... n>
struct ct_integers_list {
    template <size_t m>
    struct push_back
    {
        typedef ct_integers_list<n..., m> type;
    };
};

template <size_t max>
struct ct_iota_1
{
    typedef typename ct_iota_1<max-1>::type::template push_back<max>::type type;
};

template <>
struct ct_iota_1<0>
{
    typedef ct_integers_list<> type;
};

/***************************
// return a subset of a tuple
***************************/
template <size_t... indices, typename Tuple>
auto tuple_subset(const Tuple& tpl, ct_integers_list<indices...>)
    -> decltype(std::make_tuple(std::get<indices>(tpl)...))
{
    return std::make_tuple(std::get<indices>(tpl)...);
    // this means:
    //   make_tuple(get<indices[0]>(tpl), get<indices[1]>(tpl), ...)
}

/***************************
// return the tail of a tuple
***************************/
template <typename Head, typename... Tail>
inline std::tuple<Tail...> tuple_tail(const std::tuple<Head, Tail...>& tpl)
{
    return tuple_subset(tpl, typename ct_iota_1<sizeof...(Tail)>::type());
    // this means:
    //   tuple_subset<1, 2, 3, ..., sizeof...(Tail)-1>(tpl, ..)
}

/***************************
// increment every element in a tuple (that is referenced)
***************************/
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
increment(std::tuple<Tp...>& t)
{ }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<(I < sizeof...(Tp)), void>::type
increment(std::tuple<Tp...>& t)
{
    std::get<I>(t)++ ;
    increment<I + 1, Tp...>(t);
}

/**************************** 
// check equality of a tuple
****************************/
template<typename T1>
inline bool not_equal_tuples( const std::tuple<T1>& t1,  const std::tuple<T1>& t2 )
{
    return (std::get<0>(t1) != std::get<0>(t2));
}

template<typename T1, typename... Ts>
inline bool not_equal_tuples( const std::tuple<T1, Ts...>& t1,  const std::tuple<T1, Ts...>& t2 )
{
    return (std::get<0>(t1) != std::get<0>(t2)) && not_equal_tuples( tuple_tail(t1), tuple_tail(t2) );
}

/**************************** 
// dereference a subset of elements of a tuple (dereferencing the iterators)
****************************/
template <size_t... indices, typename Tuple>
auto dereference_subset(const Tuple& tpl, ct_integers_list<indices...>)
    -> decltype(std::tie(*std::get<indices-1>(tpl)...))
{
    return std::tie(*std::get<indices-1>(tpl)...);
}

/**************************** 
// dereference every element of a tuple (applying operator* to each element, and returning the tuple)
****************************/
template<typename... Ts>
inline auto
  dereference_tuple(std::tuple<Ts...>& t1) -> decltype( dereference_subset( std::tuple<Ts...>(), typename ct_iota_1<sizeof...(Ts)>::type()))
  {
    return dereference_subset( t1, typename ct_iota_1<sizeof...(Ts)>::type());
  }


template< typename T1, typename... Ts >
class zipper
{
    public:

    class iterator : std::iterator<std::forward_iterator_tag, std::tuple<typename T1::value_type, typename Ts::value_type...> >
    {
        protected:
            std::tuple<typename T1::iterator, typename Ts::iterator...> current;
        public:

        explicit iterator(  typename T1::iterator s1, typename Ts::iterator... s2 ) : 
            current(s1, s2...) {};

        iterator( const iterator& rhs ) :  current(rhs.current) {};

        iterator& operator++() {
            increment(current);
            return *this;
        }

        iterator operator++(int) {
            auto a = *this;
            increment(current);
            return a;
        }

        bool operator!=( const iterator& rhs ) {
            return not_equal_tuples(current, rhs.current);
        }

        typename iterator::value_type operator*() {
            return dereference_tuple(current);
        }
    };


    explicit zipper( T1& a, Ts&... b):
                        begin_( a.begin(), (b.begin())...), 
                        end_( a.end(), (b.end())...) {};

    zipper(const zipper<T1, Ts...>& a) :
                        begin_(  a.begin_ ), 
                        end_( a.end_ ) {};

    template<typename U1, typename... Us>
    zipper<U1, Us...>& operator=( zipper<U1, Us...>& rhs) {
        begin_ = rhs.begin_;
        end_ = rhs.end_;
        return *this;
    }

    zipper<T1, Ts...>::iterator& begin() {
        return begin_;
    }

    zipper<T1, Ts...>::iterator& end() {
        return end_;
    }

    zipper<T1, Ts...>::iterator begin_;
    zipper<T1, Ts...>::iterator end_;
};



//from cppreference.com: 
template <class T>
  struct special_decay
  {
     using type = typename std::decay<T>::type;
  };

//allows the use of references:
template <class T>
 struct special_decay<std::reference_wrapper<T>>
 {
   using type = T&;
 };

template <class T>
 using special_decay_t = typename special_decay<T>::type;

//allows template type deduction for zipper:
template <class... Types>
 zipper<special_decay_t<Types>...> zip(Types&&... args)
 {
   return zipper<special_decay_t<Types>...>(std::forward<Types>(args)...);
 }


我要问一些事情:


参考和性能:从理论上讲,(我认为)唯一应该复制的是迭代器,但是我不确定如何确认这一点。我希望这几乎没有开销(也许有几个指针取消引用?),但是我不确定如何检查类似的东西。在我的用例中?从技术上讲,该代码实际上并不是按“预期的”方式工作(它应该吐出“ auto i”值的副本,并且只允许使用“ auto& i”修改原始容器的值,并且应该有一个版本可以让您查找,但不能触摸:“ const auto& i”),但我不确定如何解决该问题。我希望我需要为const模式创建一个const auto& i版本,但是我不确定如何为auto i版本制作副本。
代码清洁度,最佳实践:我的代码几乎从未被其他人读过因此,最好的建议是任何建议或评论。我也不确定如何处理move构造函数:应该删除它们还是忽略它们?


评论

我喜欢special_decay的把戏。

我没有达到std :: get <0>(i)= 5.0的目的;线。是否应该修改元组?我的第三个循环输出仍然是1,a,5 \ n 2,b,4 \ n 3,c,3 \ n 4,d,2

#1 楼

我没什么好说的。您的代码读起来很不错,这很令人愉快。不过,这里有一些花絮:

typedef

如果您愿意编写现代代码,则应考虑删除typedef并在各处使用using。它有助于使常规别名和别名模板之间保持一致。此外,=符号有助于在视觉上拆分新名称及其引用的类型。而且语法在声明变量的方式上也是一致的:

auto i = 1;
using some_type = int;


完美转发

很明显,您已经使用了它。但是在其他一些地方也可以使用它:

template <size_t... indices, typename Tuple>
auto tuple_subset(Tuple&& tpl, ct_integers_list<indices...>)
    -> decltype(std::make_tuple(std::get<indices>(std::forward<Tuple>(tpl))...))
{
    return std::make_tuple(std::get<indices>(std::forward<Tuple>(tpl))...);
    // this means:
    //   make_tuple(get<indices[0]>(tpl), get<indices[1]>(tpl), ...)
}


std::enable_if

虽然在返回类型中使用std::enable_if功能,我发现它倾向于使其不可读。因此,您可能需要将其移至模板参数列表。考虑您的代码:

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<(I < sizeof...(Tp)), void>::type
increment(std::tuple<Tp...>& t)
{
    std::get<I>(t)++ ;
    increment<I + 1, Tp...>(t);
}


并将其与以下代码进行比较:

template<std::size_t I = 0, typename... Tp,
        typename = typename std::enable_if<I < sizeof...(Tp), void>::type>
inline void increment(std::tuple<Tp...>& t)
{
    std::get<I>(t)++ ;
    increment<I + 1, Tp...>(t);
}


后增量

根据类型,++var可能比var++快。对于int,它不会改变任何内容,但是如果您的容器包含大型字体,请记住,通常将++中的var++定义为:

auto operator++(int)
    -> T&
{
    auto res = var;
    ++var;
    return res;
}


如您所见,复制增量变量并调用++var。因此,您可能想在一般情况下使用++var而不是var++。而不是const zipper<U1, Us...>&

template<typename U1, typename... Us>
zipper<U1, Us...>& operator=(zipper<U1, Us...>& rhs) { ... }


如果希望这组功能完整且朝着一致的方向,则可能还需要提供功能zipper<U1, Us...>&begin() constend() constcbegin() const。 STL。另外,一些cend() const对于operator==会非常有用。

另外,我喜欢新的函数语法,恕我直言,IMHO可以帮助分离函数返回类型及其名称,这在返回类型很长的情况下特别有用。但是,我读到有些人不喜欢它,所以这取决于您自己的喜好。

结论

通常来说,您的代码很好并且可以正常工作,更好。我提供了一些技巧,但是您可能还可以做很多其他事情来改进它。尽管涉及到可读性,但是它始终取决于您,首选项在该域中很重要:)

编辑:好的,显然您的示例运行良好,但是@Barry的回答似乎突出了更严重的问题。您可能想接受它。

#2 楼

其他两个答案还没有提出一些严重的问题。

取消引用问题

假设我正在压缩vector<T>vector<U>。迭代器的current成员将具有std::tuple<std::vector<T>::iterator, std::vector<U>::iterator>类型,其value_type将是std::tuple<T, U>,其reference类型将是std::tuple<T, U>&

首先,后者毫无意义。没有给您提供std::tuple<T,U>reference,因此您应该更改该特定的typedef以引用value_type(只需提供std::iterator的所有类型,而不使用默认值)。在这里:

typename iterator::value_type operator*() {
    return dereference_tuple(current);
}


dereference_tuple在这里做对了-它为您提供了所有引用,因此该表达式的类型为std::tuple<T&, U&>。但这不是您要返回的内容!因此,不是产生引用,而是产生值。这意味着您将在每次迭代中复制每个元素,并且不允许进行任何形式的修改。

但是缺少修改是隐藏的。您的原始示例可以很好地编译:

for (auto i : d) {
    std::get<0>(i) = 5.0;
}


,但实际上并没有修改a中的任何内容,而{1.0, 2.0, 3.0, 4.0}仍然保留。这对您的用户而言非常令人惊讶。您绝对要确保返回参考元组。

确实不支持转发

您的主要功能是:

template <class... Types>
zipper<special_decay_t<Types>...> zip(Types&&... args)


,但是您的构造函数是:

explicit zipper( T1& a, Ts&... b):
                    begin_( a.begin(), (b.begin())...), 
                    end_( a.end(), (b.end())...) {};


您不能使用rvalues来调用它。如果我尝试执行zip(foo(), bar()),它将无法编译。总比不工作要好!但是,如果实际上可以支持,那就太好了。

如果您不打算支持它,则应该更改签名以使其显而易见:

template <class... Cs>
zipper<special_decay_t<Cs>...> zip(Cs&... containers)


const绝对不受支持

当前,const容器无法压缩。举例说明:

std::vector<int> v{1, 2, 3};
const std::vector<char> c{'a', 'b', 'c'};
zip(v, c); // error


由于多种原因,这将无法编译。首先,您要使用decay类型,所以您要构造一个zipper<std::vector<int>, std::vector<char>>,因此您只能从那里失败-无法获得正确的类型。接下来,您将使用T::iterator,在这种情况下,我们需要const_iterator。最后,您得到的是value_type。首先,我们要产生reference(请参见第一部分),但是在这种情况下,我们需要const_reference。因此,这一切都需要处理。

使用C ++ 11,您可以使用declval更直接地获得所有这些信息:

template <typename T>
using iter_t = decltype(std::declval<T&>().begin());

template <typename T>
using ref_t = decltype(*std::declval<iter_t<T>>());


然后currentstd::tuple<iter_t<Ts...>>(请参见稍后介绍为什么我放弃T1),您可以得出std::tuple<ref_t<Ts...>>

begin()和end()由ref?

如果您只想在基于范围的表达式中支持迭代,则此方法很好用,但会导致如果人们开始将zipper用作其他地方的普通容器,则各种各样的破损代码。我认为最好是按价值回报。

可能的默认操作

您的副本构造函数完全执行默认操作,因此请明确说明:

zipper(const zipper& rhs) = default;


您的赋值运算符具有误导性。首先,它不是复制分配运算符(编译器将默认它,因为复制分配运算符永远不是模板)。而且为什么还要支持其他任意拉链的分配?那将永远可行吗?让我们默认它为:

zipper& operator=(const zipper& rhs) = default;


简化模板

您拥有zipper<T1, Ts...>,但是T1从来都不是特别的。您只用它来表示您无法使用zipper<>。但是,如果只是将它作为static_assert,则可以大大缩短代码的其余部分:许多中间对象。这完全没有必要。而不是为每个元素创建全新的元组,只需砍下一个索引:

template <class... Ts>
class zipper {
    static_assert(sizeof...(Ts) > 0, "!");

public:
    class iterator 
    : std::iterator<std::forward_iterator_tag,
        std::tuple<typename Ts::value_type...>
        >
    {
        ...
    };

    explicit zipper(Ts&... containers)
    : begin_(containers.begin()...)
    , end_(containers.end()...)
    { }

    // etc.
};


扩展器技巧

首先,not_equals_tuplect_integers_list正在重新发明轮子。我们有ct_iota_1。如果您没有C ++ 14编译器,只需从Web的某个地方复制它的实现即可。它在所有元编程中都非常有用,因此如果每个人都使用相同的术语将很有用。

接下来,您可以编写各种“顺序迭代”函数而无需递归。以std::integer_sequence为例:

template <class Tuple>
bool any_equals(Tuple const&, Tuple const&, std::index_sequence<> ) {
    return false;
}

template <class Tuple, std::size_t I, std::size_t Is...>
bool any_equals(Tuple const& lhs, Tuple const& rhs, std::index_sequence<I, Is...> ) {

    return std::get<I>(lhs) == std::get<I>(rhs) ||          // this one
        any_equals(lhs, rhs, std::index_sequence<Is...>{}); // rest of them
}

bool operator==(iterator const& rhs) {
    return any_equals(current, rhs.current, std::index_sequence_for<Ts...>{});
}

bool operator!=(iterator const& rhs) { return !(*this == rhs); }


需要一段时间才能习惯,但是一旦您习惯了,一切都在同一地方,而无需increment

#3 楼

对于C ++ 11,您的某些元编程机制有些复杂。

比较:

template <size_t... n>
struct ct_integers_list {
    template <size_t m>
    struct push_back
    {
        typedef ct_integers_list<n..., m> type;
    };
};

template <size_t max>
struct ct_iota_1
{
    typedef typename ct_iota_1<max-1>::type::template push_back<max>::type type;
};

template <>
struct ct_iota_1<0>
{
    typedef ct_integers_list<> type;
};
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
increment(std::tuple<Tp...>& t)
{ }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<(I < sizeof...(Tp)), void>::type
increment(std::tuple<Tp...>& t)
{
    std::get<I>(t)++ ;
    increment<I + 1, Tp...>(t);
}


与:

template<size_t... n> struct ct_integers_list {};

template<size_t... acc> struct ct_iota_1;
template<size_t max, size_t... acc> struct ct_iota_1 : ct_iota_1<max-1, max-1, acc...> {};
template<size_t... acc> struct ct_iota_1<0, acc...> : ct_integers_list<acc...> {};

template<size_t... Indices, typename Tuple>
inline void increment_helper(Tuple& t, ct_integers_list<Indices...>)
{
    std::initializer_list<int>{
        [&]{ ++std::get<Indices>(t); return 0; }()...
    };
}

template<typename... Tp>
inline void increment(std::tuple<Tp...>& t)
{
    increment_helper(t, ct_iota_1<sizeof...(Tp)>());
}


我们的想法是让参数包扩展为您提供所有在C ++ 03中必须通过foo<T>::type typedef和std::enable_if完成的功能。 >
基本上,您可以用迭代替换很多递归(如我的increment_helper所示);对于必须保留递归的内容,您可以使其看起来更整洁并避免实体的扩散(如Occam的Razor)。如果您不需要所有这些中间ct_iota_1<...>::type实体,请摆脱它们!

但是,如果您想要具有生产质量的ct_integers_list,则应该使用C ++ 14的预定义std::integer_sequence,至少Xeo这样的高效实现。编译器通常将模板递归限制为256个级别。您的版本和我的版本都会很快遇到此限制,而Xeo的版本可以正常工作,因为它的递归是O(log max)而不是O(max)。