std::bind与lambda表达式

bind-vs-lambda

参考:bind-vs-lambda
参考:why-use-stdbind-over-lambdas-in-c14
参考:Lambdas vs. std::bind in C++11 and C++14


背景 —— lambda和std::bind的来源

In C++98, such binding was accomplished via std::bind1st and std::bind2nd. TR1 added std::tr1::bind, which was promoted to std::bind in C++11. But C++11 also introduced lambda expressions, and they’re slated to become even more powerful in C++14. That means that there are now two mechanisms in C++ for binding functions to arguments for later calls: std::bind and lambda expressions

在C++11之前,boost里就已经提供了boost::bindboost::lambda两个函数,在C++11到来之后,boost的bind部分代码合到了std::bind里,lambda部分则合到了C++的lambda代码里,从此使用lambda更加方便了。自从C++11,用到bind的代码就很少了,大多数都可以用lambda表达式来替代了。


lambda基础

参考:Lambda expressions in C++

lambda表达式、简称为lambda,其实就是匿名函数,lambda表达式的写法最多可以有六个部分:

  • capture clause (又名 lambda-introducer):就是前面的[]
  • parameter list Optional. ( lambda declarator)又名:就是()对应的参数列表
  • mutable specification Optional:mutable关键字
  • exception-specification Optional:throw()对应的抛出异常
  • trailing-return-type Optional:函数返回类型
  • lambda body:函数体{...}

其中,2、3、4、5都是可以省略不写的,比如一个完整的lambda函数写法:

// [=]号代表里面需要捕获的参数都是值传递进来的
[=] () mutable throw() ->int 
{
	int n = x + y;
	x = y;
	y = n;

	return n;
}

关于Capture Clause,[]代表函数不需要任何额外捕获的变量,函数需要的变量都通过()参数列表传入,[=]代表用到的非参数列表里的其他变量,都是值传递的,[&]则全是引用传递的,还可以有特殊情况的写法:

// total变量是引用捕获, factor是值捕获
[&total, factor]
// 与上相反
[factor, &total]
// 除了factor是值捕获, 其他都是引用捕获
[&, factor]
// 除了total是引用捕获, 其他都是值捕获
[=, &total]

举一些例子:

// 例一: 按照绝对值大小排序
std::sort(x, x + n, [](float a, float b) { return (std::abs(a) < std::abs(b)); });

C++14带来的Generic Lambda

参考:How does generic lambda work in C++14?

起因是在写游戏引擎的时候看到这么一行代码:

m_Window->SetEventCallback([this](auto&&... args) -> decltype(auto)
{
 	return this->Application::OnEvent(std::forward<decltype(args)>(args)...); 
});

这里用到的auto&&...貌似涉及到了模板元编程,但我又根本没有看到模板的<>,查了一下,这是Generic Lambda的使用范围,它是编译器为我们生成的模板。

先来看看什么是Generic Lambda

当Lambda的参数列表里出现至少一个auto类型的参数,那么此Lambda就是一个Generic Lambda表达式,比如:

// 1
auto glambda = [] (auto a) { return a; };

// 2
auto f = [](auto a, auto b) { return a + b; };
std::cout << f(1, 4) << std::endl;// print 5
std::cout << f(string("ff"), string("aa")) << std::endl;// print ffaa

拿第一个例子来说,它会被编译器更改为:

class /* unnamed */
{
public:
    template<typename T>
    T operator () (T a) const { return a; }
};

generic lambdas are just syntactic sugar for unique, unnamed functors with a templated call operator

总的来说,泛型lambda就是一种syntactic sugar,是编译器创建的带模板operator的匿名functor。那么再回到原本的问题,下面这个lambda表达式为:

[this](auto&&... args) -> decltype(auto)
{
 	return this->Application::OnEvent(std::forward<decltype(args)>(args)...); 
}

可以翻译为:

class /* unnamed */
{
public:
    template<typename ...args>
    T operator () (T&&... args) const { this->Application::OnEvent(std::forward<decltype(args)>(args)...);  }
};

总结

这里直接给两个结论:

  • C++11里,有一些情况下,只能使用std::bind,不可以使用lambda表达式
  • 从C++14起,任何std::bind都可以用lambda表达式来代替,因为泛型Lambda的出现让Lambda开始支持polymorphic

关于第一点,有这么一些区别:

  • C++11里的lambda表达式,其capture list里只能捕获lvalues,但std::bind可以使用右值,比如auto f1 = std::bind(f, 42, _1, std::move(v));
  • Expressions can't be captured, only identifiers can,而std::bind可以写:auto f1 = std::bind(f, 42, _1, a + b);
  • std::bind支持Overloading arguments for function objects
  • lambda表达式Impossible to perfect-forward arguments

最后贴一些不太重要的东西

附录

下面是一个C++11使用bind的例子:

struct foo
{
	// 定义一个functor
    template < typename A, typename B >
    void operator()(A a, B b)
    {
        cout << a << ' ' << b;
    }
};

auto f = bind(foo(), _1, _2);
f( "test", 1.2f ); // will print "test 1.2"

C++11用泛型lambda会报的错

C++0x lambdas are monomorphic, while bind can be polymorphic.

在C++11(或者C++0x)里,这样的代码是编译错误的,因为此时的lambda表达式是单态的

auto f = [](auto a, auto b) { cout << a << ' ' << b; }
f("test", 1.2f);

本来我打算用VS2017编译这段代码的,发现能够正常运行,查了下,应该VS2017最低也是用的C++14,所以我找了个在线C++11网站执行这段代码,编译报错,信息如下:

$g++ -std=c++11 -o main *.cpp
main.cpp: In function 'int main()':
main.cpp:6:14: error: use of 'auto' in lambda parameter declaration only available with -std=c++14 or -std=gnu++14
  auto f = [](auto a, auto b) { cout << a << ' ' << b; };
              ^~~~
main.cpp:6:22: error: use of 'auto' in lambda parameter declaration only available with -std=c++14 or -std=gnu++14
  auto f = [](auto a, auto b) { cout << a << ' ' << b; };
                      ^~~~
main.cpp:7:16: error: no match for call to '(main()::<lambda(int, int)>) (const char [5], float)'
  f("test", 1.2f);
                ^
main.cpp:7:16: note: candidate: 'void (*)(int, int)' <conversion>
main.cpp:7:16: note:   conversion of argument 2 would be ill-formed:
main.cpp:7:4: error: invalid conversion from 'const char*' to 'int' [-fpermissive]
  f("test", 1.2f);
    ^~~~~~
main.cpp:6:28: note: candidate: 'main()::<lambda(int, int)>' <near match>
  auto f = [](auto a, auto b) { cout << a << ' ' << b; };
                            ^
main.cpp:6:28: note:   conversion of argument 1 would be ill-formed:
main.cpp:7:4: error: invalid conversion from 'const char*' to 'int' [-fpermissive]
  f("test", 1.2f);
    ^~~~~~

报错信息,显示,C++11的lambda函数的参数列表里,参数类型不可以为auto

posted @ 2022-12-03 13:07  弹吉他的小刘鸭  阅读(360)  评论(0编辑  收藏  举报