首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++11's基于循环的“类Zip”功能

C++11's基于循环的“类Zip”功能
EN

Code Review用户
提问于 2013-09-05 22:26:56
回答 2查看 27.4K关注 0票数 30

我的目标是让以下代码正常工作:

代码语言:javascript
复制
#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;
    }
}

具有输出的

代码语言:javascript
复制
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“的来源是:

代码语言:javascript
复制
#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)...);
 }

我想要几件事:

  • 引用和性能:理论上,唯一需要复制的东西(我相信)是迭代器,但我不知道如何确认。我希望这有很少的开销(可能会有几个指针取消引用?),但我不知道如何检查这样的东西。
  • 正确性:在我的用例中是否有隐藏的bug没有出现?从技术上讲,代码并不像“预期的”那样工作(它应该提供"auto i“值的副本,并且只允许用"auto& i”修改原始容器值,并且应该有一个版本允许您查看,但不允许触摸:"const auto& i"),但我不确定如何解决这个问题。我希望我需要为const模式创建一个const auto& i版本,但我不知道如何为auto i版本创建一个副本。
  • 代码整洁,最佳实践:我的代码几乎从来没有被其他人读过,所以任何关于最佳实践或评论的建议都会受到欢迎。我也不知道如何处理移动构造函数:是删除它们,还是忽略它们?
EN

回答 2

Code Review用户

回答已采纳

发布于 2013-11-15 10:46:20

我没什么好说的。您的代码读起来相当好,这是相当令人愉快的。不过,以下是一些小道消息:

typedef

如果您愿意编写现代代码,则应该考虑删除typedef并在任何地方使用using。它有助于在普通别名和别名模板之间保持一致。此外,=符号有助于直观地分割新名称和它所引用的类型。语法与声明变量的方式也是一致的:

代码语言:javascript
复制
auto i = 1;
using some_type = int;

完美转发

很明显你已经用过了。但是,在其他一些地方,使用它是有意义的:

代码语言:javascript
复制
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时,我发现它往往使其不可读。因此,您可能希望将其移到模板参数列表中。考虑一下您的代码:

代码语言:javascript
复制
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);
}

把它和这个比较一下:

代码语言:javascript
复制
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++中的++通常定义为:

代码语言:javascript
复制
auto operator++(int)
    -> T&
{
    auto res = var;
    ++var;
    return res;
}

如您所见,将创建增量变量的另一个副本,并调用++var。因此,您可能希望在泛型上下文中使用++var而不是var++

杂项消息

代码语言:javascript
复制
template<typename U1, typename... Us>
zipper<U1, Us...>& operator=(zipper<U1, Us...>& rhs) { ... }

您可能希望传递一个const zipper<U1, Us...>&而不是一个zipper<U1, Us...>&

代码语言:javascript
复制
zipper<T1, Ts...>::iterator& begin() {
    return begin_;
}

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

您还可能希望提供函数begin() constend() constcbegin() constcend() const,如果您希望这组函数对STL是完整的和一致的。另外,一些operator==对于zipper::iterator来说也是很好的。

另外,我喜欢IMHO帮助分隔函数返回类型及其名称的新函数语法,当该返回类型是长的时,它特别有用。然而,我读到有些人不喜欢它,所以这取决于你自己的喜好。

结论

一般来说,您的代码是好的,它的工作,这是更好的。我提供了一些提示,但可能还有许多其他事情可以做,以改善它。当它涉及到可读性时,它总是由您决定的,但是,首选项在该领域很重要:)

编辑:好的,显然是yout的例子很好,但是@Barry的答案似乎突出了更严重的问题。你可能想接受它。

票数 15
EN

Code Review用户

发布于 2013-12-24 01:03:02

对于C++11来说,您的一些元编程机器有点过于复杂。

比较:

代码语言:javascript
复制
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);
}

相对于:

代码语言:javascript
复制
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、typedefs和std::enable_if等所做的事情。

基本上,您可以用迭代替换大量递归(就像在我的increment_helper中那样);对于必须保持递归的部分,您可以使它看起来更整洁一些,并避免实体的扩散(àla Occam's Razor)。如果您不需要所有这些中间ct_iota_1<...>::type实体,那么就把它们处理掉吧!

但是,如果你想要一个生产质量的ct_integers_list,你应该使用C++14's预定义的std::integer_sequence,或者至少是一个高效的实现,比如这个是Xeo写的。编译器通常将模板递归限制在大约256个级别;您的版本和我的版本都会很快遇到这个限制,而Xeo的则会运行得很好,因为它的递归是O(log max)而不是O(max)。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/30846

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档