【发布时间】:2015-12-10 16:35:44
【问题描述】:
这是对我遇到的另一个问题的简化,但它本身就很好。这个想法是在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,但也因为我想研究如何做到这一点。基于 cmets,下面是一个示例,说明如何在没有多态 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());
}
为了能够传递模板“函数”,我们需要将它包装在一个对象中:
struct print1 {
template <class Type> const Type& operator()(Type&& obj) {
std::cout << obj << " ";
return obj;
}
};
现在可以将其传递给函数,类型查找将在参数包扩展点完成:
mapcar(print1(), make_tuple(2, "Hello", 3.5));
【问题讨论】:
-
在 C++14 中,您不再需要指定函数的返回类型。您可以简单地将
auto指定为返回类型,不再需要-> decltype(...)。 -
你必须通过Functor,你不能通过generic函数。
-
顺便说一句,这是一个很好的问题!
-
stackoverflow.com/users/2684539/jarod42 是的,这是真的。在这里研究 C++ 11 和 C++ 14 可以处理的内容。
标签: c++ templates c++11 c++14 template-meta-programming