函数式c++使用auto映射组合器
Functional C++ map combinator using auto
我尝试使用花哨的-std=c++14特性来实现函数式语言中的"map"组合符(不要与std::map混淆)。我的最终目标是为函数式编程编写一个"facade模式"头,使我能够在大多数时候忘记副作用和迭代器。我在https://gist.github.com/phatak-dev/766eccf8c72484ad623b上找到了一个志同道合的人的帖子。Madhukara版本的地图看起来像
template <typename Collection,typename unop>
Collection map(Collection col,unop op) {
std::transform(col.begin(),col.end(),col.begin(),op);
return col;
}
只要您不要求一些愚蠢的东西,它似乎可以完美地工作,但返回类型必须与输入集合相同。我的尝试概括为拥有不同类型的域和范围如下:
template <typename Collection, typename function>
auto map(function f, Collection c) {
auto result;
std::transform(c.begin(),c.end(),result.begin(),f);
return result;
}
这不能编译,但希望它清楚的人我想做什么…我想初始化一个f的输出类型为c的空的same-type-of-container-as,然后把f(c[i])
- s放进去。编译器抱怨说,"自动结果"的声明没有初始化,但我不知道如何要求一个空的无论什么。有没有办法调整这条线,让它做我想做的事?我以前从来没有尝试过用auto做这么奇特的事情,所以欢迎任何额外的建议。
谢谢!
约翰编辑:这里有一个希望合理的例子:
auto first_letter = [](string s) { return s[0]; }
vector<string> words;
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map(first_letter, words); // {'h','w'}
编辑2:这是另一种使用重量级"流"库(不要与IO流混淆)来实现"迭代器模式"的方法,如Java的streams:
http://jscheiny.github.io/Streams/Java方法:http://tutorials.jenkov.com/java-collections/streams.html
这种流方法允许对容器类型有更多的选择自由(就像一些答案似乎支持的那样)和延迟求值。
直接使用boost:: adapters:: transformed:
#include <boost/range/adaptor/transformed.hpp>
template <typename Collection, typename function>
auto map(function f, Collection c) {
return c | boost::adaptors::transformed(f);
}
使用这个范围-你可以创建任何你想要的容器。
char first_letter(string s) { return s[0]; }
vector<string> words;
words.push_back("hello"); words.push_back("world");
auto transformed_range = map(first_letter, words);
vector<char> first_letters(begin(transformed_range ), end(transformed_range ));
如果您坚持让map
函数返回容器,而不是range -在此函数模板中添加一个参数:
#include <boost/range/adaptor/transformed.hpp>
template <typename Result, typename Collection, typename function>
auto map(function f, Collection c) {
auto transformed_range = c | boost::adaptors::transformed(f);
return Result(begin(transformed_range), end(transformed_range));
}
char first_letter(string s) { return s[0]; }
vector<string> words;
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map<vector<char>>(first_letter, words);
但是如果你真的坚持要有你想要的确切的行为-你必须知道如何将集合类型转换为其他具有转换值的集合类型。
第一个-使用new_value_type:
的方法template <typename Function, typename OldValueType>
struct MapToTransformedValue
{
using type = decltype(std::declval<Function>()(std::declval<OldValueType>()));
};
总体特征:
template <typename Function, typename Container>
struct MapToTransformedContainer;
最简单的情况-对于std::array
:
// for std::array
template <typename Function, typename OldValueType, std::size_t N>
struct MapToTransformedContainer<Function, std::array<OldValueType, N>>
{
using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
using type = std::array<value_type, N>;
};
对于std::vector
—稍微复杂一点—您需要提供新的分配器,对于std分配器—您可以使用它的重新绑定模板:
// for std::vector
template <typename Function, typename OldValueType, typename OldAllocator>
struct MapToTransformedContainer<Function, std::vector<OldValueType, OldAllocator>>
{
using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
using allocator = typename OldAllocator::template rebind<value_type>::other;
using type = std::vector<value_type, allocator>;
};
所以你的函数看起来像这样:
template <typename Collection, typename function>
auto map(function f, Collection c)
{
using NewCollectionType = typename MapToTransformedContainer<function, Collection>::type;
auto transformed_range = c | boost::adaptors::transformed(f);
return NewCollectionType (begin(transformed_range), end(transformed_range));
}
现在-你的main()
是所需的:
char first_letter(std::string const& s) { return s[0]; }
int main() {
std::vector<std::string> words;
words.push_back("hello"); words.push_back("world");
auto first_letters = map(first_letter, words);
std::cout << first_letters[0] << std::endl;
}
请注意,对于value_type
由Key,Value
对组成的其他容器,如std::map
, std::set
(以及它们的unordered_…你必须定义MapToTransformedContainer
的另一个专门化
我将利用大多数容器都有接受一对迭代器的构造函数这一事实。
#include <boost/iterator/transform_iterator.hpp>
template <typename Function, typename Collection>
struct map_to
{
Function f;
const Collection& c;
template <typename T>
operator T() &&
{
using std::begin; using std::end;
return { boost::make_transform_iterator(begin(c), f)
, boost::make_transform_iterator(end(c), f) };
}
};
template <typename Function, typename Collection>
map_to<Function, Collection> map(Function f, const Collection& c)
{
return { f, c };
}
测试:
int main()
{
std::vector<std::string> words;
words.push_back("hello");
words.push_back("world");
auto first_letter = [](std::string s) { return s[0]; };
std::vector<char> v = map(first_letter, words);
std::set<char> s = map(first_letter, words);
std::forward_list<char> f = map(first_letter, words);
}
只是为了好玩,下面是我对这个问题的尝试:
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
template <template<typename...> class Collection>
struct mapper {
template<typename function, typename T, typename... Rest>
static auto map(function f, const Collection<T, Rest...>& c) {
Collection<decltype(f(*c.begin()))> result;
std::transform(c.begin(),c.end(),std::inserter(result, result.end()),f);
return result;
}
};
int main()
{
// Example 1
std::vector<int> v{0, 1};
auto fv = mapper<std::vector>::map([](const auto& val) { return val + 0.1f; }, v);
for (const auto& f : fv) { std::cout << f << " "; }
std::cout << "n";
// Example 2
std::set<float> m{1, 2, 3, 4};
auto fm = mapper<std::set>::map([](const auto& val) { return static_cast<int>(val / 2.0f); }, m);
for (const auto& f : fm) { std::cout << f << " "; }
}
请注意,只有当您对输出容器的类型参数以外的所有参数的默认值满意时,这才会起作用。
我也有同样的任务,如果这意味着减少样板文件,在功能上妥协是可以接受的。
这是我最终的结果(可能不适用于使用多个显式参数的容器,或者其模板参数无法简单推断,但您会同意它使用起来很干净)。
using std::vector;
template<typename Src, typename Dst, template<class, typename ...> typename Container>
Container<Dst> fmap(Container<Src>& container, Dst(*f)(Src)) {
Container<Dst> result;
result.reserve(container.size());
std::transform(container.begin(), container.end(), std::back_inserter(result), f);
return result;
}
int main() {
vector<int> a = {1, 2, 3};
auto f = [](int x) -> double { return (x + 1)/2; };
vector<double> b = fmap(a, +f);
for (auto x: b) {
std::cout << x << std::endl;
}
return 0;
}
对我来说,一个主要的要求是不要让它使用起来很难看,而且它不应该使用boost。
编辑:如果你试图用一个非标准的分配器传递一个向量,这也会表现得很愚蠢(读:不编译)。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 将函数类成员映射到类本身内部
- 如何在OMNET++中指定与命令行参数组合的输出文件名
- 如何在 C# 中映射双 C 结构指针?
- 如何在C++中使用结构生成映射
- 使用std::函数映射对象方法
- 如何加载(或映射)文件部分的最大大小,但适合在Windows上的RAM
- 可组合的lambda/std::函数与std::可选
- C++映射分割错误(核心转储)
- 内联映射初始化的动态atexit析构函数崩溃
- C++ 有没有办法以相同的效率组合此列表和映射
- 如何使用 CGAL 简化组合映射
- 组合字符串 C++ 的映射和数组
- 我什么时候应该首选写入组合 CUDA 分配的映射主机内存
- 在C++中使用映射的值的组合
- 函数式c++使用auto映射组合器
- 在本征中,如何将多个映射矩阵组合为矩阵数组
- c++使用无序键组合进行映射查找
- Yaml-cpp(新API) -嵌套映射/序列组合和迭代