这是我所遇到的另一个问题的简化,但它本身就很好。我们的想法是在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,还因为我想调查一下如何使用它。基于这些注释,下面是一个如何在没有多态lambda(需要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());
}
为了能够传递模板“function”,我们需要将它包装在一个对象中:
struct print1 {
template <class Type> const Type& operator()(Type&& obj) {
std::cout << obj << " ";
return obj;
}
};
现在可以将其传递给函数,类型查找将在参数包扩展时完成:
mapcar(print1(), make_tuple(2, "Hello", 3.5));
答案 0 :(得分:3)
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);
}
答案 1 :(得分:3)
我错过了什么?
#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), ...);
}
如果你的下一个问题是&#34;为什么括号?&#34;。答案是因为 fold表达式仅在表达式的上下文中进行评估。 f(arg1), f(arg2);
是一个声明。