使用C++中的参数包进行函数式编程
Functional programming using parameter packs in C++
这是对我遇到的另一个问题的简化,但它本身就很好。其思想是在Scheme中实现类似于map
和apply
的功能基元。
简单回顾一下:在Scheme中,给定函数f
,则(apply f '(1 2 3))
等价于(f 1 2 3)
,(map f '(1 2 3))
等价于((f 1) (f 2) (f 3))
。
实现apply
是一件容易的事情,还有很多其他问题可以说明如何做到这一点:
template <class Func, class... Args, std::size_t... Ixs>
auto apply_helper(Func&& func, const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(func(get<Ixs>(args)...))
{
return forward<Func>(func)(get<Ixs>(forward<const tuple<Args...>&>(args))...);
}
template <class Func, class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto apply(Func&& func, const tuple<Args...>& args)
-> decltype(apply_helper(func, args, Ixs()))
{
return apply_helper(forward<Func>(func),
forward<const tuple<Args...>&>(args), Ixs());
}
void print3(int x, const char* s, float f) {
cout << x << "," << s << "," << f << endl;
}
int main() {
auto args = make_tuple(2, "Hello", 3.5);
apply(print3, args);
}
现在实现map
,这有点棘手。我们希望这样的东西能起作用,所以这就是目标(这里使用mapcar
来避免与std::map
冲突):
template <class Type>
bool print1(Type&& obj) {
cout << obj;
return true;
}
int main() {
auto args = make_tuple(2, "Hello", 3.5);
mapcar(print1, args);
}
用于传递print1
函数的其他替代方案也是可以的。
因此,如果我们对函数进行硬编码,以下代码将正常工作:
template <class... Args, std::size_t... Ixs>
auto mapcar_helper(const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(make_tuple(print1(get<Ixs>(args))...))
{
return make_tuple(print1(get<Ixs>(forward<const tuple<Args...>&>(args)))...);
}
template <class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(const tuple<Args...>& args)
-> decltype(mapcar_helper(args, Ixs()))
{
return mapcar_helper(forward<const tuple<Args...>&>(args), Ixs());
}
问题是,我们如何将此代码泛化为接受任意名称作为输入,并让它解决模板内的名称查找问题?仅仅添加一个模板参数是不起作用的,因为它无法解决此时的函数重载问题。
我们想调用上面的mapcar
,相当于代码:
make_tuple(print1(2), print1("Hello"), print1(3.5));
更新:最初的挑战之一是让它与C++11编译器一起工作,部分原因是我使用的是GCC 4.8,但也因为我想研究如何做到这一点。根据评论,下面是一个如何在没有多态lambdas(需要C++14编译器支持)的帮助下实现的示例。
它并不像我希望的那样简单,C++14的功能会让它变得更容易,但至少它可以在给用户带来轻微不便的情况下得到支持。
template <class Func, class... Args, std::size_t... Ixs>
auto mapcar_helper(Func&& func, const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(make_tuple(func(get<Ixs>(args))...))
{
return make_tuple(func(get<Ixs>(args))...);
}
template <class Func, class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(Func&& func, const tuple<Args...>& args)
-> decltype(mapcar_helper(func, args, Ixs())
{
return mapcar_helper(forward<Func>(func), forward<decltype(args)>(args), Ixs());
}
为了能够传递模板"函数",我们需要将其封装在一个对象中:
struct print1 {
template <class Type> const Type& operator()(Type&& obj) {
std::cout << obj << " ";
return obj;
}
};
现在可以将其传递给函数,并在参数包扩展点进行类型查找:
mapcar(print1(), make_tuple(2, "Hello", 3.5));
template <typename F, class... Args, std::size_t... Ixs>
auto mapcar_helper(F f, const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(make_tuple(f(get<Ixs>(args))...))
{
return make_tuple(f(get<Ixs>(args))...);
}
template <typename F, class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(F f, const tuple<Args...>& args)
-> decltype(mapcar_helper(move(f), args, Ixs()))
{
return mapcar_helper(move(f), args, Ixs());
}
然后你做:
mapcar([](auto&& obj) { return print1(std::forward<decltype(obj)>(obj)); }, args);
也许我不明白这个问题。您需要将print1
封装在lambda中,因为它在其他情况下是不明确的;您想通过print1
的哪个实例化?
如果你没有宏观恐惧症,你可以使用一个宏:让它变得更优雅
#define LIFT(F) ([&](auto&&... args) -> decltype(auto) {
return F(::std::forward<decltype(args)>(args)...);
})
然后您可以使用mapcar(LIFT(print1), args)
。
这就是我如何编写自己的map
函数:
template<typename F, class Tuple, std::size_t... Is>
auto map(Tuple&& tuple, F f, std::index_sequence<Is...>)
{
using std::get;
return std::tuple<decltype(f(get<Is>(std::forward<Tuple>(tuple))))...>{
f(get<Is>(std::forward<Tuple>(tuple)))...
};
}
template<typename F, class Tuple>
auto map(Tuple&& tuple, F f)
{
using tuple_type = std::remove_reference_t<Tuple>;
std::make_index_sequence<std::tuple_size<tuple_type>::value> seq;
return (map)(std::forward<Tuple>(tuple), std::move(f), seq);
}
我错过了什么?
#include <iostream>
#include <string>
template<class F, class...Args>
void map(F&& f, Args&&...args)
{
using expander = int[];
(void) expander { 0, ((void) f(args), 0)... };
}
auto main() -> int
{
using namespace std;
map([](const auto& x) { cout << x << endl; }, 1, "hello"s, 4.3);
return 0;
}
预期输出:
1
hello
4.3
注意,在c++17中,map()
函数变得更令人愉快:
template<class F, class...Args>
void map(F&& f, Args&&...args)
{
(f(args), ...);
}
如果你的下一个问题是"为什么使用括号?"。答案是因为折叠表达式仅在表达式的上下文中求值。f(arg1), f(arg2);
是一个语句。
参考:http://en.cppreference.com/w/cpp/language/fold
- 有一个打印语句的函数是一种糟糕的编程实践吗
- C++使用模板元编程生成函数
- 在C++中编程,将 3 个数字发送到一个函数,然后计算这 3 个数字的平均函数
- 如何使用模板元编程在自由函数C++链接两个不相关的类
- 返回不停止函数,递归函数问题?(编程练习,动态规划,Levenshtein 回溯)
- 以编程方式在 C++ 中创建函数
- 创建析构函数是好的编程实践吗?
- 当您希望在Arduino Uno编程中同时执行不同函数时,使用什么代码/语句
- 使用C++元编程提取 C 函数的参数("Practical C++ Metaprogramming" 中的示例)
- 函数模板编程而不是继承
- 可视化 在C++编程中编写一个函数星号
- C++函数式编程.实施 f(a)(b)(c)
- C :对输入验证函数进行编程的性能影响可以对其进行编程,而每次都可以将其编程
- 是否有任何函数可用于在 c++ 编程中传输流程,以替代 while 或 do while 循环
- 在使用英特尔内部函数对 SIMD 代码进行编程时,如何强制使用 vmovapd 而不是 vmovupd?
- CPP 元编程:包含元组函数
- 模板元编程:检查稍后定义的函数是否存在
- 是c++模板元编程的一种函数编程形式
- 为什么Erlang和其他函数编程语言不是原生的c/c++
- 形成递归函数C++编程