jbo*_*rnl 4 c++ template-meta-programming variadic-templates c++14
我是一名回归 C++ 程序员,他已经离开该语言好几年了(当我上次活跃在该语言中时,C++11 才刚刚开始获得真正的吸引力)。在过去的几年里,我一直在积极地用 Python 开发数据科学应用程序。作为恢复速度的学习练习,我决定在 C++14 中实现 Python 的 zip() 函数,现在有了一个工作函数,可以接受任何两个 STL(和其他一些)容器,其中包含任何类型和“zip”将它们转换为元组向量:
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;
}
Run Code Online (Sandbox Code Playgroud)
例如,以下代码(取自运行 xeus-cling C++14 内核的 Jupyter notebook 中的代码单元)
#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) << ") ";
Run Code Online (Sandbox Code Playgroud)
产生这个输出:
(1, 6.01) (2, 7.02) (3, 8.03)
Run Code Online (Sandbox Code Playgroud)
我想扩展我的函数以获取任意数量的任意类型的容器,并且我花了一些时间研究可变参数模板,但令我尴尬的是,我只是没有连接点。我如何将这个函数概括为采用任意数量的任意容器类型来保存任意数据类型?我不一定要寻找我需要的确切代码,但我真的可以使用一些帮助来了解如何在这种情况下利用可变参数模板。
此外,对我所拥有的代码的任何批评都将不胜感激。
可变参数模板的机制与 Python 传递函数位置参数然后将这些位置参数扩展为值序列的能力并无太大不同。C++ 的机制更强大,更基于模式。
所以让我们从顶部开始。您想采用任意系列的范围(容器太有限了):
template <typename ...Ranges>
auto pyzip(Ranges&& ...ranges)
Run Code Online (Sandbox Code Playgroud)
...这里的使用指定了一个包的声明。这个特殊的函数声明声明了两个“包”:一个名为 的类型包Ranges和一个名为 的参数包ranges。
因此,您需要做的第一件事是获取一系列开始和结束迭代器。由于这些迭代器可以是任意类型,数组不行;它必须存储在tuple. 元组的每个元素都需要通过获取ranges、获取该元素并调用begin它来初始化。这是你如何做到的:
auto begin_its = std::make_tuple(begin(std::forward<Ranges>(ranges))...);
Run Code Online (Sandbox Code Playgroud)
这种使用...称为包扩展。其左侧的表达式包含一个或多个包。然后...将这个表达式转换成逗号分隔的值序列,替换列出包的包的每个相应成员。被扩展的表达式是begin(std::forward<Ranges>(ranges))。并且我们在此处同时使用ranges和Ranges,因此两个包一起展开(并且必须具有相同的大小)。
我们将此包扩展为 的参数make_tuple,以便该函数为包中的每个元素获取一个参数。
当然,同样的事情也适用end。
接下来,您想将每个范围的元素的副本(?)存储在vector<tuple>. 嗯,这就需要我们先弄清楚范围的值类型是什么。在示例中使用的 typedef 上使用另一个包扩展很容易:
using vector_elem = std::tuple<std::decay_t<decltype(*begin(std::forward<Ranges>(ranges)))>...>;
std::vector<vector_elem> result;
Run Code Online (Sandbox Code Playgroud)
请注意,在这种情况下, the...不适用于“表达式”,但它执行相同的操作:std::decay_t为 的每个元素重复该部分ranges。
接下来,我们需要计算最终列表的大小。这……实际上出奇的困难。有人可能认为你可以只使用begin_itsand end_its,然后迭代它们,或者对它们使用一些包扩展的诡计。但是不,C++ 不允许您执行其中任何一项操作。这些元组不是包,您不能(轻松)这样对待它们。
在一个表达式中重新计算开始/结束迭代器并采用差异实际上更容易。
auto size = std::min({std::distance(begin(std::forward<Ranges>(ranges)), end(std::forward<Ranges>(ranges)))...});
result.reserve(std::size_t(size));
Run Code Online (Sandbox Code Playgroud)
好吧,就代码行数而言“更容易”,可读性不高;
std::min 这里需要一个初始化值列表来计算最小值。
对于我们的循环,比起直到迭代器达到结束状态,更容易循环计数。
但这实际上只是推动了最后一个问题。也就是说,我们有这个迭代器元组,我们需要对它们的成员执行 2 个操作:取消引用和递增。我们在做什么并不重要;这同样很难在C ++中。
哦,这是完全可行的。你只需要一个新功能。
看,您不能使用tuple运行时索引访问 a 的元素。并且您不能遍历编译时值。因此,您需要某种方法来获取包含不是参数或类型,而是整数索引的包。这组索引可以解包到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>{});
}
Run Code Online (Sandbox Code Playgroud)
apply 这里接受一个函数和一个元组,并将元组解包到函数的参数中,返回函数返回的任何内容。
所以,回到我们的函数中,我们可以apply做我们需要的事情:间接,然后增加每个元素。泛型 lambda 允许我们有效地处理将值插入到vectorthrough 中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);
}
Run Code Online (Sandbox Code Playgroud)
unused并且它的初始化器是 C++ 的一种混淆方式,它只是对包中的每个项目执行表达式,丢弃结果。不幸的是,这是在 C++14 中最直接的方法。
| 归档时间: |
|
| 查看次数: |
153 次 |
| 最近记录: |