我是一个返回的C++程序员,他已经离开这个语言好几年了(当我最后一次使用该语言时,C++11刚刚开始获得真正的吸引力)。在过去的几年里,我一直在积极地用Python开发数据科学应用程序。作为一项恢复速度的学习练习,我决定在C++14中实现Python的zip()函数,现在有了一个工作函数,它可以将任何两个包含任何类型的STL (和其他几个)容器“压缩”到一个元组向量中:
template <typename _Cont1, typename _Cont2>
auto pyzip(_Cont1&& container1, _Cont2&& container2) {
using std::begin;
using std::end;
using _T1 = std::decay_t<decltype(*container1.begin())>;
using _T2 = std::decay_t<decltype(*container2.begin())>;
auto first1 = begin(std::forward<_Cont1>(container1));
auto last1 = end(std::forward<_Cont1>(container1));
auto first2 = begin(std::forward<_Cont2>(container2));
auto last2 = end(std::forward<_Cont2>(container2));
std::vector<std::tuple<_T1, _T2>> result;
result.reserve(std::min(std::distance(first1, last1), std::distance(first2, last2)));
for (; first1 != last1 && first2 != last2; ++first1, ++first2) {
result.push_back(std::make_tuple(*first1, *first2));
}
return result;
}例如,下面的代码(摘自运行xeus-cling C++14内核的木星笔记本中的代码单元格)
#include <list>
#include <xtensor/xarray.hpp>
list<int> v1 {1, 2, 3, 4, 5};
xt::xarray<double> v2 {6.01, 7.02, 8.03};
auto zipped = pyzip(v1, v2);
for (auto tup: zipped)
cout << '(' << std::get<0>(tup) << ", " << std::get<1>(tup) << ") ";产生这个输出:
(1, 6.01) (2, 7.02) (3, 8.03)我想将我的函数扩展到任意数量的任意类型的容器,我花了一些时间研究各种模板,但让我尴尬的是,我只是没有将这些点连接起来。我如何将这个函数推广到任意数量的包含任意数据类型的任意容器类型?我不一定要寻找我所需要的确切代码,但我确实需要一些帮助来解决如何在这个场景中利用各种模板的问题。
此外,任何对我的代码的批评都将不胜感激。
发布于 2020-10-14 05:35:24
Variadic模板具有一种与Python传递函数位置参数并随后将这些位置参数扩展为值序列的能力相似的机制。C++的机制更强大,更基于模式。
所以让我们从头开始。您想要采取任意的一系列范围(容器太有限):
template <typename ...Ranges>
auto pyzip(Ranges&& ...ranges)这里使用的...指定了一个包的声明。这个特殊的函数声明声明了两个“pack”:一个名为Ranges的类型包和一个名为ranges的参数包。
因此,您需要做的第一件事是获得一系列的开始和结束迭代器。由于这些迭代器可以是任意类型的,所以数组不能这样做;它必须存储在tuple中。元组的每个元素都需要通过接受ranges、获取该元素并在其上调用begin来初始化。你是这样做的:
auto begin_its = std::make_tuple(begin(std::forward<Ranges>(ranges))...);这种对...的使用称为包扩展。左边的表达式包含一个或多个包。...接受该表达式并将其转换为逗号分隔的值序列,替换包中列出的每个对应成员。正在展开的表达式是begin(std::forward<Ranges>(ranges))。我们在这里同时使用ranges和Ranges,所以两个包都是一起展开的(而且必须是相同大小的)。
我们将这个包扩展到make_tuple的参数中,以便函数为包中的每个元素获取一个参数。
当然,同样的事情也适用于end。
接下来,您想要存储副本(?)vector<tuple>中每个范围的元素。这要求我们首先确定范围的值类型是什么。使用在示例中使用的ty胡枝子的另一个包扩展是非常容易的:
using vector_elem = std::tuple<std::decay_t<decltype(*begin(std::forward<Ranges>(ranges)))>...>;
std::vector<vector_elem> result;请注意,在本例中,...不应用于“表达式”,但它做的是相同的事情:对ranges的每个元素重复std::decay_t部分。
接下来,我们需要计算最终列表的大小。那是..。其实很难。人们可能会认为,您可以只使用begin_its和end_its,只需对它们进行迭代,或者对它们使用一些pack扩展的花招。但是不,C++不允许你这样做。这些元组不是包,你不能(很容易)把它们当作包。
实际上,只需重新计算开始/结束迭代器并接受差异就更容易了,所有这些都是在一个表达式中完成的。
auto size = std::min({std::distance(begin(std::forward<Ranges>(ranges)), end(std::forward<Ranges>(ranges)))...});
result.reserve(std::size_t(size));嗯,在代码行方面“更容易”,而不是可读性;
这里的std::min接受一个值的初始化列表来计算最小值。
对于我们的循环来说,与其一直到迭代器到达结束状态,更容易的是只循环一次计数。
但这实际上是在推卸最后一个问题。也就是说,我们有这个迭代器的元组,我们需要对它们的成员执行两个操作:去引用和递增。不管我们做的是哪一个,在C++中也同样困难。
哦,这是完全可行的。你只需要一个新的功能。
请参阅,您不能使用运行时索引访问tuple的元素。而且不能循环编译时值。因此,您需要一些方法来获得一个包,它不是包含参数或类型,而是包含整数索引。可以将这组索引解压缩到get<Index>调用中,以便tuple访问其内容。
C++17为我们提供了一个方便的std::apply函数来完成这类事情。不幸的是,这是C++14,所以我们必须写一个:
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
return f(std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
return detail::apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value>{});
}apply在这里接受一个函数和一个元组,并将元组解压到函数的参数中,返回函数返回的任何内容。
因此,回到我们的函数中,我们可以使用apply来完成我们需要的事情:间接,然后递增每个元素。并且泛型lambda允许我们有效地通过emplace_back将值插入到emplace_back中。
for (decltype(size) ix = 0; ix < size; ++ix)
{
apply([&result](auto&& ...its) mutable
{
result.emplace_back(*its...); //No need for redundant `make_tuple`+copy.
}, begin_its);
apply([](auto& ...its)
{
int unused[] = {0, (++its, 0)...};
}, begin_its);
}unused及其初始化器是C++只对包中的每个项执行表达式,从而丢弃结果的一种混乱方式。很不幸,在C++14中,这是最简单的方法。
https://stackoverflow.com/questions/64346603
复制相似问题