本部分内容包括decltype和lambda。
一、decltype
By using the new decltype keyword, you can let the compiler find out the type of an expression. This is the realization of the often requested typeof feature. However, the existing typeof implementations were inconsistent and incomplete, so C++11 introduced a new key word. For example:
map<string, float> coll;
decltype(coll)::value_type elem;
One application of decltype is to declare return types. Another is to use it in metaprogramming or to pass the type of a lambda.
(1) 用来声明返回类型
假设定义一个add函数接受两个不同的模板参数,这样在定义模板函数阶段就无法写出该函数的返回类型,这时候就可以使用decltype声明这个模板函数的返回类型,如下所示:
template <typename T1, typename T2>
decltype(x + y) add(T1 x, T2 y);
但是这样写会报错,因为编译器在看到decltype中的x和y的时候这两个变量还没被声明。在C++11中,可以把返回类型写在参数的后面,像这样就对了:
template <typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x + y);
(2) 用在元编程(metaprogramming)之中
相当于是在模板当中的定义,虽然metaprogramming有更深刻的含义。
template <typename T>
void test_decltype(T obj)
{
typedef typename decltype(obj)::iterator iType;
decltype(obj) anotherObj(obj);
}
(3) 得到lambda表达式的类型
auto cmp = [](const Person& p1, const Person& p2){
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname() < p2.firstname());
};
...
std::set<Person, decltype(cmp)> coll(cmp);
比如定义这么一个set,不仅需要lambda的对象,还需要将lambda的类型传到set中。
二、lambda
C++11 introduced lambdas, allowing the definition of inline functionality, which can be used as a parameter or a local object.
我的理解是:可以把lambda理解为一个对象,所以它可以赋值给别的对象,也有类型。比较特殊的是,它是一个可调用对象,在后面加小括号可以直接调用,还有就是如果需要这个对象的类型,一般只能通过decltype得到。
下面举一个最简单的不带参数的lambda的例子:
/************这是一个lambda****************/
[] {
std::cout << "Hello lambda" << std::endl;
};
/********在后面加小括号可以直接调用*********/
[] {
std::cout << "Hello lambda" << std::endl;
}(); //prints "Hello lambda"
/*****可以把这个对象传给另一个可调用对象*****/
auto I = []{
std::cout << "Hello lambda" << std::endl;
};
...
I(); //prints "Hello lambda"
1. lambda的通用格式:
(1) 开头的中括号可以capture外面的非静态对象,写成类似[x]的形式表示传值,[&x]表示传引用。这种写法也可以省略,比如只写[=]代表所有对象都是传值,至于用了那些对象,在lambda里面直接写就好了;再比如[=, &y]表示对y传引用,其他都是传值,但是侯捷老师不建议省略写,因为可读性比较差,所以以后用的时候还是要把每个使用的变量都写上(除非用到的特别多,一般也不会用到很多)。
(2)小括号里面放参数,这个和其他可调用对象都是一样的
(3)小括号后面还有三个可选的东西:mutable表示是否可以修改捕获的值,如果是按值传递不写mutable不能在lambda中改变那个捕获的对象,如果是按引用传递写不写mutable都能改;throwSpec表示抛出异常的情况;最后的-> retType可以指出返回类型。要注意的是,这三个东西都是可选的,如果都不写,甚至可以省略前面的小括号,但只要有一个,前面的小括号就必须写,对于返回类型来说,如果不写,编译器会对返回值自动进行类型推导。
2. 对于lambda,编译器会产生怎样的代码?
int tobefound = 5;
auto lambda1 = [tobefound](int val){
return val == tobefound;
};
//编译器会将其装换为一个类似这样的对象
class UnNamedLocalFunction
{
private:
int localVar;
public:
UnNamedLocalFunction(int var) : localVar(var) { }
bool operator()(int val)
{
return val == localVar;
}
};
capture的对象会变成这个class的成员(当然不是完全一样的,因为lambda的捕获还有一些规则,这个class并没有体现出来。)
3. 将lambda的类型作为模板参数
假设需要向set里面传递自定义的比较类型,使用lambda可以这样写:
auto cmp = [](const Person& p1, const Person& p2){
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname() < p2.firstname());
};
...
std::set<Person, decltype(cmp)> coll(cmp);
注意到这里不仅需要用decltype把lambda的类型传给模板,还需要将lambda对象作为参数传递给定义的set对象,这是为什么呢?看下面的set源码我们可以找到答案:
template <class Key,
class Compare = less<Key>,
class Alloc = alloc>
class set{
public:
//typedefs
......
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree<key_type, value_type,
identity<value_type>,
key_compare, Alloc> rep_type;
rep_type t; //red-black tree representing set
public:
......
set() : t(Compare()) {}
explicit set(const Compare& comp) : t(comp) {}
};
注意到,如果我们不把lambda对象作为参数传给set对象,就将会调用set的默认构造函数,而set的默认构造函数版本又将会调用传入的可调用类型的默认构造函数,但是lambda这种类型并没有默认构造函数和赋值运算符,所以编译器会报错(use of deleted function......);将lambda对象作为参数传给set对象,会调用set的另一个构造函数,这个构造函数可以正常使用。
4. function object 和 lambda的对比
function objects就是指重载了函数调用运算符的class,函数对象有几个不易使用的部分:需要定义整个类,较为繁琐;定义和使用的地方是分开的;the function objects defined as data members are not in-lined,所以效率上会稍微差一点。而lambda表达式解决了function objects的上述缺点。