C++14与lambda

关于C++14

据Hurb Sutter在题为“C++的未来”的演讲中透露,C++标准委员会在推出C++11标准之后,并未作过多休整即已投入到下两代标准(C++14以及C++17)的制定工作之中。其中预计于明年推出的C++14被定位于修正并完善目前的C++11标准。尽管时间并不宽裕,下一代标准C++14中仍然包含了一些引人关注的新特性。从目前已有的提案来看,C++14中最大的新特性莫过于lambda以及普通函数功能的扩展。下面是一些很有可能加入新标准的相关内容。
 

自动推导函数以及lambda返回值的类型

现状:lambda返回值的类型在两种情况下可以由编译器自动推导,无需明确指定。
//lambda的函数体内没有return语句时,返回值类型为void
[](){std::cout << "Hello";};
//显式指明返回值类型的写法如下
[]()->void{std::cout << "Hello";};

//lambda的函数体内仅包含一句return语句时,返回值类型等同于该语句中表达式的类型
[](int x){return x + 1;};
//该return语句表达式x + 1的类型为int,所以返回值类型为int
//显式指明返回值类型的写法如下
[](int x)->int{return x + 1;};
缺陷:大多数情况下编译器都不具备自动推导lambda以及函数返回值类型的能力。
//对于lambda,只要有返回值,且函数体包含一句语句以上时,
//其返回值的类型就无法由编译器自动推导,必须明确指定
[](int x)->int{int y = x + 1; return y;};

//对于普通函数或函数模板,即便函数体只包含一句语句
//其返回值的类型也无法由编译器自动推导,必须明确指定
int f(int x){return x + 1;}

//对于普通函数或函数模板,即便函数采用返回值类型后置语法
//其返回值的类型也无法由编译器自动推导,必须明确指定
template<typename T, typename U>
auto mul(const T& t, const U& u) -> decltype(t * u)
{
	return t * u;
}
对策:大多数情况下编译器将具有自动推导lambda以及函数返回值类型的能力。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//lambda返回值的类型可以交由编译器自动推导,无须明确指定
[](int x){int y = x + 1; return y;};

//普通函数返回值的类型只需指定为auto
//编译器会自动推导其返回值的类型
auto f(int x){return x + 1;}

//函数模板返回值的类型只需指定为auto
//编译器会自动推导其返回值的类型
template<typename T, typename U>
auto mul(const T& t, const U& u)
{
	return t * u;
}

单态lambda与多态lambda

现状:当前标准中的lambda属于单态(monomorphic)lambda,即lambda参数的类型必须明确指定,不能通过上下文由编译器自动推导。
std::for_each( begin(v), end(v), [](decltype(*begin(v)) x){ std::cout << x; } );
//for_each算法函数用于遍历集合
//该算法要求第三个参数是一个能接收集合元素作为唯一参数的函数或函数对象
//所以此处作为算法函数第三个参数的lambda本身的唯一参数x的类型只能是集合v的元素类型
//由于编译器没有根据上下文自动推导lambda参数类型的功能
//这里集合v的元素x的类型必须明确给出:decltype(*begin(v))
缺陷:当前标准中不存在多态(polymorphic)lambda或泛型(generic)lambda。也就是说语言中缺少参数类型可以由编译器根据上下文自动推导的lambda。
试比较以下功能类似的C#代码:
lst.ForEach(x => { Console.WriteLine("{0}", x); });
//这里List<T>的ForEach方法用于遍历List
//该方法要求一个能接收集合元素类型的委托作为参数
//所以此处作为该方法参数的lambda本身的唯一参数x的类型只能是集合lst的元素类型
//由于C#编译器具有根据上下文自动推导lambda参数类型的功能
//这里无须给出lst的元素x的类型
对策:增加泛型(generic)lambda(参数类型可以由编译器根据上下文自动推导的lambda)这一新特性。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//目前泛型lambda的语法形式尚有争议
//语法形式一(最初的提案,标准委员会讨论时争议较大,很难通过)
std::for_each( begin(v), end(v), [](x){ std::cout << x; } );
//语法形式二(可行方案)
std::for_each( begin(v), end(v), [](&x){ std::cout << x; } );
//语法形式三(可行方案)
std::for_each( begin(v), end(v), []<>(&x){ std::cout << x; } );
//语法形式四(经改进的提案,C++讨论组中争议较大,但相对容易通过)
std::for_each( begin(v), end(v), [](auto& x){ std::cout << x; } );

带模板参数的lambda

现状及缺陷:当前标准中不存在带有模板参数的lambda。
//无法将下面的函数模板改写为等价的lambda
template<typename T, typename U>
auto mul(const T& t, const U& u) -> decltype(t * u)
{
	return t * u;
}
对策:增加带有模板参数的lambda这一新特性。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//带有模板参数的lambda
auto mul = []<typename T, typename U>(const T& t, const U& u){return t * u;}

函数体为表达式的lambda

现状:lambda的函数体仅有一种形式,即使用大括弧的代码块(block)这种形式。
//即便是最简单的lambda(函数体内仅包含一句用于返回表达式的return语句),函数体也必须采用代码块形式
auto f = [](int x){return x + 1;};

//嵌套lambda
[](int x)->function<function<int(int)>(int)>{
	return [=](int y)->function<int(int)>{
		return [=](int z){
			return x + y + z;
		};
	};
};
缺陷:与C#语言相比,最简单的lambda的写法不够简洁。
试比较以下功能类似的C#代码:
//C#允许lambda的函数体采用表达式形式。
Func<int, int> = x => x + 1;

//嵌套lambda
Func<int, Func<int, Func<int, int>>> g = x => y => z => x + y + z;
对策:允许最简单的lambda的函数体采用表达式形式。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//允许lambda的函数体采用表达式形式
auto f = [](int x)x + 1;

//嵌套lambda
[](int x)->function<function<int(int)>(int)>
	[=](int y)->function<int(int)>
		[=](int z) x + y + z;
对策:考虑到语言的一致性,普通函数及函数模板如果改用lambda语法,函数体也将被允许采用表达式形式。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//使用扩展的lambda语法
//下面的函数模板
template<typename T, typename U>
auto mul(const T& t, const U& u) -> decltype(t * u)
{
	return t * u;
}
//就可以被改写为
template<typename T, typename U>
[] mul(const T& t, const U& u) t * u;

lambda的捕获表达式

现状:通过引用捕获,值捕获等捕获方式,lambda的函数体内可以捕获并使用lambda的外围中定义的局部变量以及类成员变量。
//引用捕获
int n = 1;
[&](){
    n++; // n==2
}();
// n==2

//值捕获
int n = 1;
[=]()mutable{
    n++; // n==2
}();
// n==1
缺陷:捕获方式仍显不足,比如移动捕获就无法实现。
//没有移动捕获,只能通过参数传递
std::vector<HugeObject> p;
auto f = [](std::vector<HugeObject>&& p2){}
f(std::move(p));
对策:通过引入捕获表达式,实现通用的捕获方式。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//通过捕获表达式实现移动捕获
std::vector<HugeObject> p;
auto f = [p2 = std::move(p)](){}
f();

lambda参数的相关扩展

现状及缺陷:与普通函数相比,lambda的函数签名中没有缺省参数,也不能省略参数名。
//无法将下面的函数改写为等价的lambda
int f(int x = 0){return x + 1;}
void g(int /* unused */){std::cout << "g";}
对策:允许lambda的函数签名中带有缺省参数,允许省略参数名。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//缺省参数以及被省略的参数名
auto f = [](int x = 0){return x + 1;};
auto g = [](int /* unused */){std::cout << "g";};

具名lambda

现状及缺陷:lambda没有名字,无法重载以及递归。
//lambda可以冠名,但无法重载
auto f1 = [](int x){std::cout << "f int";};
auto f2 = [](double x){std::cout << "f double";};

//lambda无法直接递归
//auto factorial = [](int x)->int{return x == 0 ? 1 : x * factorial(x - 1);};
std::function<int(int)> factorial = [&](int x){return x == 0 ? 1 : x * factorial(x - 1);};
对策:增加具名lambda这一新特性。
注:以下C++代码中包含预想中的语言新特性,目前编译器尚不支持。
//具名lambda可以重载
[]f(int x){std::cout << "f int";};
[]f(double x){std::cout << "f double";};

//具名lambda也可以递归
[]factorial(int x)->int{return x == 0 ? 1 : x * factorial(x - 1);};
相关链接
Generic (Polymorphic) Lambda http://cpp-next.com/files/2012/09/N3418.pdf

你可能感兴趣的:(C++14与lambda)