本文介绍C++14新特性,函数返回类型推导和泛型Lambda表达式。

第一章 C++14语言特性

1.1 函数返回类型推导

本文介绍C++14新特性,函数返回类型推导 (Function Return Type Deduction)
C++的演进历程中,每一次标准更新都让这门“古老”的语言变得更加现代和易用。C++11引入了auto关键字,让我们尝到了“语法糖”的甜头,给编程带来了极大便利;而C++14将auto的功能扩展到了函数的返回值上。

1.1.1 函数返回值后置提出背景

C++11中,虽然可以使用auto推导变量类型,但是auto不能推导函数的返回值。某些情况下,让编译器推导函数的返回值,必须使用尾置返回类型(Trailing Return Type)方式实现。
在处理函数模板时,这种尾置返回值方式比较常用,使用方式如下:

    template<typename T,typename U>
    auto add(T a, U b)->decltype(a + b)
    {
		return a + b;
    }

    void test()
    {
        int ret =  add(1, 2.2);
    }

这种方式必须在函数后边写出: ->decltype(…)代码,为了看起来更直观,C++14引入了函数返回类型推导,告别了这样多余的写法。

1.1.2 函数返回类型推导功能说明

C++14中,可以直接将函数的返回类型声明为auto,不必再写尾置返回值,编译器会分析函数内return的返回值类型,并自动推导出返回值类型。
需要注意的是:auto 默认按值返回,如果需要返回引用 或者 const属性,需要在auto位置添加这些属性。
举例如下:
示例1:编译器自动推导a+b类型

    // C++11 尾置返回值
    template<typename T,typename U>
    auto add(T a, U b)->decltype(a + b)
    {
		return a + b;
    }

	// C++14 简化尾置返回值
    template<typename T,typename U>
    auto add2(T a, U b)
    {
        return a + b;
	}

    void test()
    {
        int ret =  add(1, 2.2);
		int ret2 = add2(1, 2.2);
    }

示例2:返回引用,使用auto + & 即可返回引用类型。

    int x = 10;
    // 返回引用方式:auto & 
    auto& getX()
    {
        return x;
    }

    void test()
    {
        getX() = 3;
    }

1.2 泛型Lambda

C++11中引入Lambda表达式,改变了我们编写回调函数和算法的方式,为我们带来了极大的便利。然而,C++11中的Lambda表达式有一个缺陷是,参数类型必须显示指定,比如 实现一个能同时处理int和double的lambda,往往需要写两个Lambda表达式,一个接收int,一个接收double类型,虽然处理逻辑相同,但是确引入了两个Lambda表达式。C++14为了解决这个问题,引入了泛型Lambda(Generic Lambda)。

1.2.1 Lambda表达式问题说明

在C++11中,Lambda参数类型是定义时就确定好的,不支持泛型方式。
比如,要实现处理两个double int 类型的lambda,C++11实现方式如下:

    void test()
    {
        auto sumInt = [](int a, int b) { return a + b; };
        auto sumDouble = [](double a, double b) { return a + b; };
		cout << "sumInt = " << sumInt(2, 3) << endl;
        // sumInt = 5
		cout << "sumDouble = " << sumDouble(2.5, 3.5) << endl;
        // sumDouble = 6
    }

这种方式显示增加了代码的冗余性,为了解决这个问题,可以使用函数模板,但是因为处理逻辑比较简单,直接定义函数模板,有些“杀鸡用牛刀”的感觉。
还有一种场景,在使用标准库中的算法时,如果第三个参数需要一个函数对象,此时lambda表达式的参数仍然要显示指定,如下:

    void test()
    {
        std::map<std::string, std::vector<int>> myMap;
		myMap["numbers"] = { 1, 2, 3, 4, 5 };
		myMap["name"] = { 10, 20, 30 };

		// C++11: 必须显示指定lambda参数类型
        std::for_each(myMap.begin(), myMap.end(),
            [](const std::pair<std::string, std::vector<int>>& pair) 
            {
                // ...
				cout << " key = " << pair.first << endl;

            });
        /*  输出结果:
            key = name
            key = numbers
        */
    }

上面例子中,Lambda表达式都需要显示指定明确的类型,不支持自动推导。在第二个例子中,如果参数比较长,写起来不是很方便。为此,C++14支持了泛型Lambda解决这个问题。

1.2.2 泛型Lambda实现原理

泛型Lambda允许我们在Lambda中使用auto关键字。实现原理是,编译器根据实参自动推导出参数类型。
语法格式:

auto lambda = [](auto x, auto y) {
    return x + y;
};

泛型Lambda实现原理:编译器看到泛型Lambda表达式后,将其转换成一个类模板,在这个类模板中重载了带参数的operator(),类似于:

struct CompilerGeneratedName {
    template <typename T, typename U>
    auto operator()(T x, U y) const {
        return x + y;
    }
};


下面是使用C++14实现上边的两个例子,看如何简化代码。
示例1:基于泛型lambda的改进

    void test()
    {
        auto sumInt = [](int a, int b) { return a + b; };
        auto sumDouble = [](double a, double b) { return a + b; };
		cout << "sumInt = " << sumInt(2, 3) << endl;
        // sumInt = 5
		cout << "sumDouble = " << sumDouble(2.5, 3.5) << endl;
        // sumDouble = 6

		// C++14 泛型lambda
		auto sumGeneric = [](auto a, auto b) { return a + b; };
        cout << "sumGeneric int = " << sumGeneric(2, 3) << endl;
        // sumGeneric int = 5
        cout << "sumGeneric double = " << sumDouble(2.5, 3.5) << endl;
		// sumGeneric double = 6
	
    }

示例2:泛型lambda在STL算法中的使用

    void test()
    {
        std::map<std::string, std::vector<int>> myMap;
		myMap["numbers"] = { 1, 2, 3, 4, 5 };
		myMap["name"] = { 10, 20, 30 };

		// C++11: 必须显示指定lambda参数类型
        std::for_each(myMap.begin(), myMap.end(),
            [](const std::pair<std::string, std::vector<int>>& pair) 
            {
                // ...
				cout << " key = " << pair.first << endl;

            });
        /*  输出结果:
            key = name
            key = numbers
        */

		// C++14: 使用auto自动推导参数类型
        std::for_each(myMap.begin(), myMap.end(),
            [](const auto& pair) 
            {
				 cout << " key = " << pair.first << endl;
            });
        /*  输出结果:
            key = name
            key = numbers
		*/
    }

示例3:泛型Lambda + 完美转发
下面例子中,使用Lambda泛型方式,封装一个可变参模板,然后使用使用decltype(auto)确定参数类型,进行完美转发。

    void print(int a)
    {
		cout << "int: " << a << endl;
    }

    void print(int a, int b)
    {
		cout << "int int: " << a << ", " << b << endl;
    }

    void test()
    {

        // 实现 完美转发的打印函数
        auto printer = [&](auto && ...args)
        {
              print(std::forward<decltype(args)>(args)...);
        };

        printer(22);
        // int: 22
        printer(22,33);
		// int int: 22, 33
    }

1.2.3 总结

泛型Lambda是对C+11Lambda的扩充,使用起来更方便。

更多推荐