我正在学习C ++中的函数式编程。我的意图是传递一个非泛型函数作为参数。我知道模板方法,但是我想将函数签名限制为API设计的一部分。我制定了4种不同的方法example on cpp.sh:
// Example program
#include <iostream>
#include <string>
#include <functional>
typedef int(functor_type)(int);
int by_forwarding(functor_type &&x) {
return x(1);
}
int functor_by_value(functor_type x) {
return x(1);
}
int std_func_by_value(std::function<functor_type> x) {
return x(1);
}
int std_func_by_forwarding(std::function<functor_type> &&x) {
return x(1);
}
int main()
{
std::cout << functor_by_value([](int a){return a;}); // works
std::cout << std_func_by_value([](int a){return a;}); // works
std::cout << std_func_by_forwarding(std::move([](int a){return a;})); // works
//std::cout << by_forwarding([](int a){return a;}); // how to move lambda with forwarding ?
}
以上任何尝试正确吗?如果没有,我如何实现我的目标?
答案 0 :(得分:14)
(基于评论的澄清)
可以使用std::is_invocable
来限制签名:
template<typename x_Action> auto
functor_by_value(x_Action && action)
{
static_assert(std::is_invocable_r_v<int, x_Action, int>);
return action(1);
}
答案 1 :(得分:5)
其他替代方法:
template <typename Func>
auto functor_by_value(Func&& f)
-> decltype(std::forward<Func>(f)(1))
{
return std::forward<Func>(f)(1);
}
答案 2 :(得分:5)
通常,这取决于您的编译器今天的性能如何,以及将来的性能如何。
当前,编译器并不擅长优化std::function
。令人惊讶的是,std::function
是一个复杂的对象,有时必须分配内存以维护有状态的lambda函数。这也使std::function
必须能够引用成员函数,常规函数和lambda并以透明方式进行处理的事情变得复杂。这种透明性会耗费大量运行时间。
因此,如果您想要最快的代码,则应注意std::function
。因此,第一个变体是最快的(在当今的编译器中):
int functor_by_value(functor_type x) {
return x(1);
}
它只是将指针传递给函数。
当涉及有状态的lambda时,您只有两个选择。可以将lambda作为模板参数传递,或转换为std::function
。因此,如果您想使用lambdas来实现最快的代码(在当今的编译器中),则可以将该函数作为模板参数传递。
由于lambda函数可能具有大状态,因此传递它可能会复制大状态(无法进行复制省略时)。 GCC将直接在参数列表上构造lambda(无副本),但是嵌套函数将为lambda调用副本构造函数。为避免这种情况,请通过const引用(在这种情况下它不能更改)或右值引用传递它:
template<class Func>
void run2(const Func & f)
{
std::cout << "Running\n";
f();
}
template<class Func>
void run(const Func & f)
{
run2(f);
}
int main()
{
run([s=BigState()]() { std::cout << "apply\n"; });
return 0;
}
或者:
template<class Func>
void run2(Func && f)
{
f();
}
template<class Func>
void run(Func && f)
{
run2(std::forward<Func>(f));
}
int main()
{
run([s=BigState()]() { std::cout << "apply\n"; });
return 0;
}
在不使用引用的情况下,复制lambda时将复制BigState()。
更新: 再次阅读问题后,我看到它想限制签名
template<typename Func,
typename = std::enable_if_t<
std::is_convertible_v<decltype(Func(1)), int>>>
void run2(const Func & f)
{
std::cout << "Running\n";
f();
}
这会将它限制为可以接受int
(可能使用隐式强制转换)并返回int
或任何隐式强制转换为int
的类型的函数。但是,如果您只想接受完全接受int
并返回正整数的类函数对象,则可以查看lambda是否可转换为std::function<int(int)>
答案 3 :(得分:5)
但是我想将函数签名限制为API设计的一部分。
所以要限制它:
#include <functional>
#include <type_traits>
#include <iostream>
/// @tparam F is a type which is callable, accepting an int and returning an int
template
<
class F,
std::enable_if_t
<
std::is_convertible_v<F, std::function<int(int)>>
>* = nullptr
>
int myfunc(F &&x) {
return x(1);
}
int main()
{
auto a = myfunc([](int x) { std::cout << x << std::endl; return 1; });
// does not compile
// auto b = myfunc([]() { std::cout << "foo" << std::endl; return 1; });
}