#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构造函数:应该删除它们还是忽略它们?
#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() const
,end() const
和cbegin() 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>>());
然后
current
是std::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_tuple
和ct_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
)。
评论
我喜欢special_decay的把戏。我没有达到std :: get <0>(i)= 5.0的目的;线。是否应该修改元组?我的第三个循环输出仍然是1,a,5 \ n 2,b,4 \ n 3,c,3 \ n 4,d,2