我发现这种实现是函数式编程的一些常见特性,例如map/reduce: (我知道这样的东西即将到来或部分存在于新的C++版本中)
GitHub 链接
代码的一部分:
template <typename T, typename U>
U foldLeft(const std::vector<T>& data,
const U& initialValue,
const std::function<U(U,T)>& foldFn) {
typedef typename std::vector<T>::const_iterator Iterator;
U accumulator = initialValue;
Iterator end = data.cend();
for (Iterator it = data.cbegin(); it != end; ++it) {
accumulator = foldFn(accumulator, *it);
}
return accumulator;
}
template <typename T, typename U>
std::vector<U> map(const std::vector<T>& data, const std::function<U(T)> mapper) {
std::vector<U> result;
foldLeft<T, std::vector<U>&>(data, result, [mapper] (std::vector<U>& res, T value) -> std::vector<U>& {
res.push_back(mapper(value));
return res;
});
return result;
}
使用示例:
std::vector<int> biggerInts = map<int,int>(test, [] (int num) { return num + 10; });
类型参数 T,您必须完全有资格进行编译,如示例中所示,例如 map<int,int>( ... )。 此实现适用于 C++11,如链接页面上所述。
现在是否有可能使用较新的C++版本(甚至 11 个)来使用这种不那么冗长的内容,即自动推断类型 U,T ? 我用谷歌搜索过,只发现类模板显然有一些改进,而不是函数模板,C++17 中的参数推导。 但是由于我只以相当基本的方式使用模板,我想知道是否存在我不知道的东西可以详细地改善此实现。
您可以将映射签名重写为:
template <typename T, typename M, typename U = decltype(std::declval<M>()(T{}))>
std::vector<U> map(const std::vector<T>& data, const M mapper)
然后T
将被推导出为矢量项的value_type
。
M
是任何可调用的对象。
当调用T{}
时,U
被推算为M()
函子的返回类型。
下面
std::vector<int> biggerInts = map(test, [] (int num) { return num + 10; });
^^^^ empty template arguments list
工作正常。
现场演示
更通用的模板使模板参数推断更容易。
一个原则:使用std::function
作为模板化函数的参数通常是错误的。std::function
是一种类型擦除,用于某些内容需要将某些未知的可调用内容存储为特定类型时使用。但是模板已经能够处理任何任意可调用的类型。因此,如果我们只使用通用typename FuncT
模板参数,则可以直接推断出原始指向函数的指针、lambda 或其他具有operator()
的类。
我们不妨更通用地接受任何输入容器,而不仅仅是vector
,然后从中确定T
,如果它甚至是直接需要的。
因此,对于 C++11,我会重写这些:
// C++20 is adding std::remove_cvref, but it's trivial to implement:
template <typename T>
using remove_cvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename Container, typename U, typename FuncT>
remove_cvref_t<U> foldLeft(
const Container& data,
U&& initialValue,
const FuncT& foldFn) {
remove_cvref_t<U> accumulator = std::forward<U>(initialValue);
for (const auto& elem : data) {
accumulator = foldFn(std::move(accumulator), elem);
}
return accumulator;
}
template <typename Container, typename FuncT>
auto map(const Container& data, const FuncT& mapper)
-> std::vector<remove_cvref_t<decltype(mapper(*std::begin(data)))>>
{
using T = remove_cvref_t<decltype(*std::begin(data))>;
using ResultT = std::vector<remove_cvref_t<decltype(mapper(std::declval<const T&>()))>>;
ResultT result;
foldLeft(data, std::ref(result), [&mapper] (ResultT &res, const T& value) -> ResultT& {
res.push_back(mapper(value));
return res;
});
return result;
}
请参阅关于大肠杆菌的工作计划。
旧map
有一个不幸的事情:它有可能在每次迭代时复制结果向量。accumulator = foldFn(accumulator, *it);
中的=
是自分配,它可能不执行任何操作,或者可能会分配新内存、复制内容,然后释放旧内存并更新容器。因此,在这种情况下,我将foldLeft
的U
更改为std::reference_wrapper
。在这种情况下,=
仍将包装器"重新绑定"到同一对象,但这至少会很快。
在 C++14 及更高版本中,您可以使用通用 lambda 来取消在map
中查找T
:[&mapper] (std::vector<U>& res, const auto& value)
...