c++14新增语法和标准库特性


C++14的内容相对于C++11要少很多。某种程度上,C++14可以理解为C++11的补充完善。

1. New language features

1.1. Tweaked wording for contextual conversions

C++14之前存在着隐式的类型转换,C++14中调整了上下文转换的措辞。

#include <iostream>
#include <string>

class A {
public:
    A(int x) : value(x) {}
    int value;
};

class B {
public:
    explicit B(const A& a) : value(a.value) {}
    int value;
};

void func(B b) {
    std::cout << "B value: " << b.value << std::endl;
}

int main() {
    A a(10);
    // 在C++11中,下面的调用将编译错误,因为A类型不能隐式转换为B类型。
    // func(a);

    // 在C++14中,通过上下文转换规则,可以将a对象隐式转换为B类型,以匹配函数参数要求。
    func(a);

    return 0;
}

在这个示例程序中,定义了两个类A和B,其中B类的构造函数被显式地标记为explicit,以防止隐式转换。然后,定义了一个接受B类型参数的函数func。
在main函数中,首先创建了一个A类型的对象a,并尝试将它作为实参传递给func函数。在C++11中,这个调用将会导致编译错误,因为A类型不能隐式转换为B类型。但在C++14中,由于上下文转换规则的改进,编译器会自动将a对象转换为B类型,以匹配函数参数要求,从而成功调用func函数并输出结果。
需要注意的是,上下文转换规则的改进并不代表应该将所有隐式转换都视为安全或可取的。在实际编程中,应该尽量避免使用隐式转换,保持代码的明确和可读性。

1.2. Binary literals

C++14 中引入了二进制字面值(binary literals),允许开发人员使用二进制数字来表示整数。这样可以使代码更加清晰和易读。
在 C++14 中,可以使用前缀 0b 或 0B 来表示二进制字面值。例如:

int x = 0b101010; // 使用二进制字面值表示整数 42

上述代码中,我们使用 0b 前缀来表示二进制字面值 101010,然后将其赋值给整型变量 x。因此,变量 x 的值为 42。
需要注意的是,二进制字面值只能用于整型类型。如果使用其他类型或不合法的二进制字面值,则会导致编译错误。

1.3. decltype(auto), Return type deduction for normal functions

C++14 中引入了 decltype(auto),它可以用来在函数返回值中进行类型推断。decltype(auto) 的作用类似于 auto 关键字,但是它可以保留返回表达式的引用或值特性,因此比 auto 更加灵活。
使用 decltype(auto) 时,可以将函数返回类型设置为自动推导,例如:

auto foo() -> decltype(auto) {
    int x = 42;
    return x;           // 返回 x 的值
}

auto bar() -> decltype(auto) {
    int x = 42;
    return (x);         // 返回 x 的引用
}

上述示例中,我们分别定义了两个函数 foo() 和 bar(),它们都使用 decltype(auto) 来推导返回类型。在 foo() 函数中,我们返回了变量 x 的值,因此 decltype(auto) 推导出的类型为 int。在 bar() 函数中,我们返回了变量 x 的引用,因此 decltype(auto) 推导出的类型为 int&。
需要注意的是,在使用 decltype(auto) 时,需要确保返回表达式是有效的,并且能够正确地推导出类型。如果返回表达式无法推导出类型,或者推导出的类型不符合函数的预期返回类型,将会导致编译错误。

1.4. generalized lambda captures

C++14 引入了初始化捕获(init-capture),也称为通用捕获(generalized lambda captures),它允许我们在 lambda 表达式中对外部变量进行初始化操作。这样可以使代码更加简洁和易读。
使用初始化捕获时,可以在捕获列表中使用 = 或 {} 符号来初始化外部变量。例如:

int x = 42;
auto lambda1 = [y = x + 1] { return y; };    // 使用 "=" 初始化捕获
auto lambda2 = [z{ x + 2 }] { return z; };   // 使用 "{}" 初始化捕获

上述示例中,我们分别定义了两个 lambda 表达式 lambda1 和 lambda2,它们都使用初始化捕获来初始化外部变量 x。
在 lambda1 中,我们使用 = 符号来表示使用默认的捕获方式,并将 y 初始化为 x + 1。因此,当我们调用 lambda1() 时,它将返回 43(即 x + 1 的值)。
在 lambda2 中,我们使用 {} 符号来手动指定捕获方式,并将 z 初始化为 x + 2。因此,当我们调用 lambda2() 时,它将返回 44(即 x + 2 的值)。
需要注意的是,初始化捕获只能用于非静态变量,并且不能与其他捕获方式(如按引用或按值捕获)混合使用。初始化捕获的引入可以使代码更加简洁,尤其是在需要对外部变量进行复杂初始化操作时。

1.5. Generic lambda expressions

通用 lambda 表达式是 C++14 引入的一个新特性,它使得 lambda 表达式可以像函数模板一样具有通用性,即能够接受任意类型的参数。使用通用 lambda 表达式可以减少重复代码,并提高代码的复用性。
通用 lambda 表达式与普通 lambda 表达式的区别在于,它使用 auto 关键字来声明参数类型。例如:

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

int a = 1, b = 2;
double c = 3.14, d = 2.72;

int result1 = lambda(a, b);     // result1 = 3
double result2 = lambda(c, d);  // result2 = 5.86

上述示例中,我们定义了一个通用 lambda 表达式 lambda,它使用 auto 关键字声明两个参数 x 和 y。在 lambda 表达式内部,我们对这些参数进行加法运算,然后返回结果。
在调用 lambda 时,我们可以传递任意类型的参数。例如,我们可以将整型变量 a 和 b 作为参数来计算它们的和,也可以将浮点型变量 c 和 d 作为参数来计算它们的和。无论传递的参数类型是什么,都可以通过编译,并得到正确的结果。
需要注意的是,在定义通用 lambda 表达式时,可以使用任何有效的 C++ 表达式来声明参数类型,并且可以在函数体内使用这些参数执行任何操作。这使得通用 lambda 表达式成为了一个强大且灵活的特性,可以在编写泛型代码时发挥重要作用。

1.6. variable templates

C++14 引入了变量模板(variable templates)的概念,它允许开发人员使用模板来声明变量并进行初始化。这样可以使代码更加灵活和可读。
与函数模板类似,变量模板也使用模板参数来指定变量的类型和值。例如:

template<typename T>
constexpr T pi_v = T(3.1415926535897932385);

double d = pi_v<double>;    // d = 3.141592653589793
float f = pi_v<float>;     // f = 3.14159265
long double ld = pi_v<long double>; // ld = 3.1415926535897932385

上述示例中,我们定义了一个变量模板 pi_v,它使用模板参数 T 来指定变量的类型,并将其初始化为圆周率 3.1415926535897932385。在调用 pi_v 时,我们可以通过显式指定模板参数来创建不同类型的变量,并对这些变量进行赋值操作。
类型别名(type alias):使用变量模板可以定义类型别名。例如,我们可以使用变量模板来定义一个别名来代替复杂的类型名称:

template<typename T>
using vector_ptr = std::unique_ptr<std::vector<T>>;

vector_ptr<int> p(new std::vector<int>());  // 等价于 std::unique_ptr<std::vector<int>>

需要注意的是,在使用变量模板时,需要确保模板参数能够正确地推导出变量的类型,并且变量的初始化表达式能够被该类型正确处理。如果存在任何问题,将会导致编译错误。
除了使用模板参数来指定变量类型之外,变量模板还可以使用默认模板参数、非类型模板参数等特性。这使得变量模板成为一个强大的工具,可以帮助开发人员在编写模板代码时更加灵活和高效。

1.7. Extended constexpr

C++14 扩展 constexpr
在 C++11 中,constexpr 函数只能包含非复杂的语句和表达式。C++14 扩展了 constexpr 的功能,允许 constexpr 函数内包含一些简单的控制语句,例如 if 和 for 循环。
这意味着可以在编译时计算更复杂的表达式和算法。使用扩展 constexpr 还可以避免运行时计算的开销,进而提高程序的性能。
需要注意的是,C++14 扩展 constexpr 的使用仍然有一些限制和规则,例如函数必须是可求值的、不能包含虚函数等等。
此外,C++14 扩展 constexpr 还增加了对 constexpr 函数返回类型的限制。在 C++11 中,constexpr 函数可以返回任何类型,只要该类型是字面值类型或者符合特定的要求。
但在 C++14 中,constexpr 函数的返回类型必须满足以下条件之一:

  • void
  • 字面值类型
  • 返回类型是一个类类型对象的引用
  • 返回类型是一个类类型对象的 rvalue 引用
  • 返回类型是一个类类型对象的 const 值

需要注意的是,在 C++14 中,如果 constexpr 函数的返回类型不符合上述条件,则该函数会被视为普通函数,而不是 constexpr 函数。
总之,C++14 扩展 constexpr 为编写更有效率且可靠的代码提供了更多的可能性。它使得我们能够在编译时进行更多的计算和优化,并减少运行时开销。
以下是一个使用C++14扩展的constexpr函数示例:

constexpr int factorial(int n) {
    if (n <= 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

static_assert(factorial(5) == 120, "failed factorial test");

在这个例子中,factorial 函数使用递归计算阶乘。在 C++11 中,这个函数无法作为 constexpr 函数,因为它包含了控制语句(if 和 else)。但是在 C++14 中,我们可以使用这个函数作为 constexpr 函数来计算 5 的阶乘,在编译期间进行计算并验证结果是否正确。
此外,C++14 还允许我们在 constexpr 函数中使用 lambda 表达式、类型别名模板和变量模板等新特性,使得编写更加灵活且可读性高的编译期代码成为可能。

1.8. Aggregates with default member initializers

在C++14中,聚合体可以具有默认成员初始化器,也就是说,聚合体的成员可以在声明时用默认值进行初始化。
举个例子,假设我们有一个简单的聚合体表示学生信息:

struct Student {
int id;
std::string name = "Unknown";
int age = 0;
};

在这个示例中,Student 是一个聚合体,它具有三个成员变量:id、name 和 age。而 name 和 age 则使用了默认成员初始化器,分别设置了默认值 “Unknown” 和 0。
使用默认成员初始化器的好处在于,我们可以在声明时指定默认值,避免在构造函数中重复写相同的初始化代码,从而使代码更加简洁。
另外需要注意的是,在使用默认成员初始化器的情况下,不能在构造函数中对该成员进行初始化,否则会导致二义性错误。例如,以下代码会导致编译错误:

struct Student {
int id;
std::string name = "Unknown";
int age = 0;

Student(int i, std::string n, int a) : id(i), name(n), age(a) {}
};

因为 name 和 age 在声明时已经有了默认值,而在构造函数中又重新对它们进行了初始化,这就导致了二义性错误。要解决这个问题,可以将 name 和 age 的默认初始化去掉,或者将它们从构造函数中移除。
总的来说,C++14 中的聚合体默认成员初始化器是一个方便且有用的特性,能够帮助我们更加简化代码并提高效率。

1.9. Omitting/extending memory allocations

C++14中的两个新特性,即扩展内存分配(extended memory allocation)和省略内存分配(omitted memory allocation),旨在改善C++程序中内存管理的效率和可读性。
省略内存分配(Omitting memory allocations)是一项优化技术,允许编译器跳过在构造函数中为实例变量分配内存的操作。这样可以减少不必要的内存分配和初始化工作,提高程序运行速度。
例如,考虑以下代码:

class MyClass {
public:
MyClass() : data(100) {}
private:
std::vector<int> data;
};

在C++11及之前的版本中,编译器会在构造函数中自动为data成员分配内存,并将其初始化为一个具有100个int元素的std::vector对象。
但是,在C++14中,我们可以使用关键字“=default”来显式地告诉编译器,我们不需要构造函数来执行数据成员的初始化,如下所示:

class MyClass {
public:
MyClass() = default;
private:
std::vector<int> data{100};
};

这里,我们使用花括号初始化语法来初始化data成员,从而避免了构造函数中的内存分配和初始化操作。两种方式理论上效率相近,只不过编译器对花括号初始化有时优化得更好一些。
另一方面,扩展内存分配(Extended memory allocation)是一种新的内存分配方式,它允许在创建对象时一次性分配多个对象的内存。对于需要频繁创建大量小对象的程序,这可以显著提高内存管理的效率。
例如,考虑以下代码:

std::vector<int> v;
for (int i = 0; i < 100; ++i) {
    v.push_back(i);
}

在C++14中,我们可以使用“reserve”函数指定vector需要分配的内存大小,从而在一次性分配所有元素的内存,避免不必要的内存分配和拷贝操作:

std::vector<int> v;
v.reserve(100);
for (int i = 0; i < 100; ++i) {
    v.push_back(i);
}

这里,我们使用“reserve”函数指定了vector需要分配的内存大小为100,从而节省了内存分配和拷贝操作所需的时间和空间。
总之,C++14中的省略内存分配和扩展内存分配是两个有用的特性,可以帮助我们更加高效地管理内存,提高程序的性能。

1.10. [[deprecated]] attribute

C++14中的[[deprecated]]属性是一个编译器指令,用于标记某个函数、变量或类型已经过时,不再建议使用。这个属性可以帮助程序员更好地管理代码库,尤其是在需要修改和更新现有代码时。当编译器检测到使用过时实体的情况时,会在编译时生成警告消息,提醒程序员更新代码。
使用[[deprecated]]属性可以让程序员逐步淘汰旧的、效率低下或不安全的功能,同时保持向后兼容性,因为它们仍然可以被其他代码调用。这对于那些需要与旧版本的代码进行交互的项目非常重要。
在C++14中,[[deprecated]]属性可以应用于函数、变量和类型。例如,下面的代码将一个函数标记为已过时:

[[deprecated("Use new_function() instead.")]]
void old_function();

在这个例子中,当程序员使用old_function()时,编译器将生成警告消息,告诉他们使用new_function()代替它。警告消息包含了开发人员提供的可选文本,以便更清楚地说明替换所需的更改。
总之,[[deprecated]]属性是C++14中非常有用的一个功能,它可以帮助程序员更好地管理和维护代码库,避免使用过时或危险的功能。

1.11. Sized deallocation

C++14中的Sized deallocation是一种内存管理特性,它允许程序员在释放内存时指定所需的字节数。这个特性在处理动态分配的内存时非常有用,因为它可以避免缺少足够内存而导致的问题。
通常情况下,释放内存时只需要使用“delete”或“delete[]”操作符即可,它们会根据对象大小自动释放所需的内存。但是,如果使用了自定义的内存分配器,则可能需要手动释放内存并指定所需的字节数。
在这种情况下,可以使用Sized deallocation,它提供了一个新的“void operator delete(void* ptr, std::size_t size)”函数签名,其中的size参数表示所要释放的内存块的字节数。例如:

class MyClass {
public:
void *operator new(std::size_t sz);
void operator delete(void* ptr, std::size_t size);
};

在这个例子中,MyClass类实现了自定义的内存分配器,并使用Sized deallocation来释放内存。在调用“delete”操作符时,编译器会自动检测是否存在这个新的函数签名,如果存在,则会调用它来释放内存。
总之,Sized deallocation是C++14中一个非常有用的内存管理特性,它可以帮助程序员更好地控制动态分配的内存,避免因为缺少内存而导致的问题。

1.12. Single quote as digit separator

在C++14中,可以使用单引号(')作为数字分隔符来提高代码的可读性。使用单引号分隔数字可以让代码更易于阅读和理解,并且不会影响数字本身的值。
例如,如果要表示一个较大的整数,可以这样写:

int num = 1'000'000;

在这个例子中,我们使用单引号将数字1000000分成了三个部分,使代码更易于阅读。
需要注意的是,单引号不能用于负数或小数点后面的数字。另外,在C++11中也支持使用单引号作为数字分隔符,但它只能用于整数类型,而在C++14中,单引号也可以用于浮点数类型。最好检查编译器是否支持此功能,以确保代码可以正常编译和运行。

2. library features

2.1. constexpr for

在C++14中,可以使用constexpr关键字来声明复数类型的运算符和函数。这个特性使得编译器能够在编译时计算出复数运算的结果,从而提高程序的性能。
例如,我们可以使用constexpr关键字来定义一个复数加法函数:

#include <complex>

constexpr std::complex<double> complex_add(const std::complex<double>& a, const std::complex<double>& b) {
    return a + b;
}

在这个例子中,我们定义了一个名为complex_add的函数,它使用constexpr关键字将其声明为编译时可计算的函数。该函数接受两个std::complex类型的参数,并返回它们的和。由于使用了constexpr关键字,编译器可以在编译时计算出结果,从而避免了在运行时进行复杂的浮点数运算。
需要注意的是,为了使用constexpr关键字,函数必须满足一定的条件,比如函数的所有输入参数和输出值都必须是可求值的常量表达式。在上述例子中,std::complex类型已经满足了这些条件。
总之,在C++14中,使用constexpr关键字可以使复数类型的运算更加高效,并且可以在编译时计算出结果,提高程序性能。

2.2. Transparent operator functors

在C++14中,可以使用透明函数对象(Transparent operator functors)来简化代码,并使其更易于阅读和理解。透明函数对象是一种特殊的函数对象,它允许将某个操作符作用于两个不同的类类型时进行隐式转换。
例如,我们可以定义一个透明函数对象,将一个字符串类型的值与任何其他类型的值相加:

struct StringAdder {
template<typename T>
auto operator()(const std::string& str, const T& value) const -> decltype(str + value) {
    return str + value;
}
};

在这个例子中,我们定义了一个名为StringAdder的透明函数对象,它接受一个字符串类型的值和任何其他类型的值,并将它们相加。该函数对象使用了模板类型参数和decltype关键字来自动判断所需返回值的类型,从而避免了手动指定返回类型的麻烦。
有了透明函数对象,我们就可以直接将其作为函数参数,并将其应用于不同的类类型。例如:

int main() {
    StringAdder adder;

    std::string str = "Hello, ";
    std::cout << adder(str, "world!") << std::endl;  // Output: "Hello, world!"

    double num = 3.14;
    std::cout << adder(str, num) << std::endl;  // Output: "Hello, 3.14"

    return 0;
}

在这个示例代码中,我们创建了一个名为adder的StringAdder对象,并将其用于不同的类类型。当我们将字符串和常量字符数组相加时,该透明函数对象会自动将字符数组转换为字符串类型,并返回它们的拼接结果。同样地,当我们将字符串和浮点数相加时,该透明函数对象会将浮点数转换为字符串类型,并返回它们的拼接结果。
总之,透明函数对象是C++14中一个非常有用的特性,可以使代码更加简洁和易于阅读,同时也支持对不同类型进行隐式类型转换,从而提高程序的灵活性和可扩展性。

2.3. std::result_of and SFINAE

在C++14中,可以使用std::result_of模板类和SFINAE(Substitution Failure Is Not An Error)技术来推断函数调用的返回类型。这个特性对于元编程和泛型编程非常有用,因为它允许我们在编译时自动计算出函数调用的返回类型,并将其作为模板参数传递给其他代码。
例如,假设我们有一个名为divide的函数,它接受两个参数并返回它们的商:

template<typename T>
T divide(T a, T b) {
    return a / b;
}

现在,我们想要编写一个函数,计算两个浮点数的商,并打印结果到控制台上。我们可以使用std::result_of模板类来自动推断返回类型,并使用SFINAE技术来处理可能的错误情况:

#include <iostream>
#include <type_traits>

template<typename T1, typename T2>
typename std::enable_if<std::is_floating_point<T1>::value && std::is_floating_point<T2>::value,
typename std::result_of<decltype(divide<float>) (T1, T2)>::type>::type
divide_and_print(T1 a, T2 b) {
    auto result = divide(static_cast<float>(a), static_cast<float>(b));
    std::cout << "Result is: " << result << std::endl;
    return result;
}

int main() {
    double x = 1.0, y = 2.0;
    divide_and_print(x, y);  // Output: "Result is: 0.5"

    return 0;
}

在这个示例代码中,我们定义了一个名为divide_and_print的函数,它接受两个参数,并计算它们的商。该函数使用SFINAE技术来限制参数类型,并使用std::result_of模板类来自动推断返回类型。
具体来说,我们使用std::enable_if模板类来检查两个类型是否为浮点数类型。如果是,则将模板参数类型设置为typename std::result_of<decltype(divide) (T1, T2)>::type,其中decltype(divide)表示divide函数的类型签名,(T1, T2)表示divide函数参数的类型,std::result_of则表示计算参数类型和返回类型后得到的结果类型。最终,我们将计算出的结果类型作为模板参数类型返回。
需要注意的是,由于使用了SFINAE技术,编译器只有在能够通过检查时才会实例化该函数。如果参数类型不是浮点数类型,或者返回类型无法计算,编译器会简单地忽略该函数并继续编译其他代码。
总之,在C++14中,可以使用std::result_of模板类和SFINAE技术来推断函数调用的返回类型,并将其作为模板参数传递给其他代码。这个特性对于元编程和泛型编程非常有用,因为它允许我们在编译时自动计算出函数调用的返回类型,并处理可能的错误情况。

2.4. constexpr for

在C++14中,标准库<chrono>提供了支持constexpr的时间点(time point)和时钟(clock)类型。这使得我们可以在编译时计算时间差,并执行其他一些与时间相关的操作。
例如,假设我们有一个需要等待一定时间的函数:

#include <chrono>
#include <thread>

void wait(int seconds) {
    std::this_thread::sleep_for(std::chrono::seconds(seconds));
}

在这个函数中,我们使用std::chrono::seconds类型来指定需要等待的时间量,然后使用std::this_thread::sleep_for函数来将当前线程暂停相应的时间量。
但是,上述代码并没有利用到constexpr关键字,无法在编译时计算出需要等待的时间。在C++14中,我们可以使用constexpr关键字修饰std::chrono::seconds类型,并利用编译时计算特性来实现更高效的代码:

#include <chrono>

constexpr std::chrono::seconds to_seconds(int seconds) {
    return std::chrono::seconds(seconds);
}

template<typename Duration>
void wait(Duration duration) {
    std::this_thread::sleep_for(duration);
}

int main() {
    constexpr auto duration = to_seconds(5);
    wait(duration);

    return 0;
}

在这个示例代码中,我们定义了一个名为to_seconds的函数,它接受一个整数值,并返回对应的std::chrono::seconds类型。该函数被声明为constexpr,因此可以在编译时计算出std::chrono::seconds类型的值。
接着,我们使用一个更通用的wait函数来等待任意时间量。该函数使用了模板类型参数,并调用了std::this_thread::sleep_for函数来休眠当前线程。由于该函数接受任意类型的时间量,因此可以重复使用,而无需为每种时间单位都编写单独的版本。
main函数中,我们使用to_seconds函数来创建一个名为durationconstexpr对象,并将其传递给wait函数进行等待操作。由于duration是一个constexpr对象,因此可以在编译时计算出相应的时间量,并且不需要在运行时执行任何计算。
总之,在C++14中,可以使用支持constexpr的标准库<chrono>来实现编译时时间计算和其他一些与时间相关的操作。这使得代码更加高效和灵活,并且可以避免运行时计算的消耗。

2.5. constexpr for

在C++14中,标准库<array>提供了支持constexpr的数组类型,并且我们可以使用constexpr关键字来定义和初始化数组。这使得我们可以在编译时计算出数组的值,并将其用作其他代码的常量表达式。
例如,假设我们有一个需要使用固定大小数组的函数:

#include <array>

template<size_t N>
int sum(const std::array<int, N>& arr) {
    int result = 0;
    for (size_t i = 0; i < N; i++) {
        result += arr[i];
    }
    return result;
}

在这个函数中,我们使用了std::array类型来表示具有固定大小的整数数组,并计算数组元素的总和。
但是,上述代码并没有利用到constexpr关键字,无法在编译时计算出数组元素的总和。在C++14中,我们可以使用constexpr关键字来定义和初始化数组,并以常量表达式形式传递给函数:

#include <array>

template<size_t N>
constexpr int sum(const std::array<int, N>& arr) {
    int result = 0;
    for (size_t i = 0; i < N; i++) {
        result += arr[i];
    }
    return result;
}

int main() {
    constexpr std::array<int, 5> arr = {1, 2, 3, 4, 5};
    static_assert(sum(arr) == 15, "sum(arr) != 15");

    return 0;
}

在这个示例代码中,我们将sum函数声明为constexpr,并使用std::array<int, N>类型来表示具有固定大小的整数数组。接着,我们在main函数中定义了一个名为arrconstexpr数组,并将其传递给sum函数。由于arr是一个constexpr数组,因此可以在编译时计算出其元素的总和。
最后,我们使用static_assert语句来检查sum(arr)的值是否等于15。由于sum(arr)是一个常量表达式,因此可以在编译时进行检查,并且不符合条件时会产生编译错误。
总之,在C++14中,可以使用支持constexpr的标准库<array>来实现编译时数组初始化,并将其用作其他代码的常量表达式。这使得代码更加高效和灵活,并且可以避免运行时计算的消耗。

2.6. constexpr for <initializer_list>, and

在C++14中,标准库<initializer_list><utility><tuple>也支持constexpr,我们可以使用它们来进行编译时计算和初始化。

  1. <initializer_list>:我们可以将std::initializer_list用作常量表达式,并在编译时计算其大小。例如:
#include <initializer_list>

template<typename T>
constexpr size_t count(const std::initializer_list<T>& list) {
    return list.size();
}

int main() {
    constexpr auto list = {1, 2, 3};
    static_assert(count(list) == 3, "count(list) != 3");

    return 0;
}

在这个示例代码中,我们将std::initializer_list用作参数类型,并计算其大小。由于list是一个constexpr对象,因此可以在编译时计算出其大小。

  1. <utility>:标准库<utility>提供了许多支持constexpr的函数和类模板,例如std::pairstd::make_pair。我们可以使用它们来实现更高效的代码。例如:
#include <utility>

template<typename T>
constexpr auto increment(T value) -> decltype(value + 1) {
    return value + 1;
}

int main() {
    constexpr auto p = std::make_pair(1, 2);
    static_assert(increment(p.first) == 2, "increment(p.first) != 2");

    return 0;
}

在这个示例代码中,我们使用std::make_pair函数创建一个名为pconstexpr对象,并调用一个名为increment的函数来增加其第一个元素的值。由于increment是一个constexpr函数,可以在编译时计算出其返回值类型。

  1. <tuple>:标准库<tuple>也支持constexpr,我们可以使用它来创建和操作元组。例如:
#include <tuple>

template<typename... Args>
constexpr auto make_tuple_sum(const std::tuple<Args...>& tuple) -> decltype(std::get<0>(tuple)) {
    constexpr size_t N = std::tuple_size<std::tuple<Args...>>::value;
    decltype(std::get<0>(tuple)) result = 0;
    for (size_t i = 0; i < N; i++) {
        result += std::get<i>(tuple);
    }
    return result;
}

int main() {
    constexpr auto t = std::make_tuple(1, 2, 3);
    static_assert(make_tuple_sum(t) == 6, "make_tuple_sum(t) != 6");

    return 0;
}

在这个示例代码中,我们定义了一个名为make_tuple_sum的函数,它接受一个包含任意类型元素的元组,并计算元组中所有整数类型元素的总和。该函数使用了std::tuple_size模板类来计算元组的大小,并使用std::get函数来访问元组的元素。
main函数中,我们使用std::make_tuple函数创建一个名为tconstexpr元组,并将其传递给make_tuple_sum函数进行计算。由于t是一个constexpr对象,因此可以在编译时计算出其元素的总和。
总之,在C++14中,标准库<initializer_list><utility><tuple>也支持constexpr,我们可以利用它们来进行编译时计算和初始化,并实现更高效的代码。

2.7. Improved std::integral_constant

在C++14中,标准库<utility>增强了std::integral_constant模板类,使其更易于使用。std::integral_constant是一个用于表示编译时常量的类模板,它包含一个类型和一个值。例如,可以使用std::integral_constant<int, 42>来表示整数值42。
在C++11中,使用std::integral_constant需要手动定义自己的类型别名。例如:

#include <type_traits>

template<int N>
struct my_int : std::integral_constant<int, N> {};

int main() {
    static_assert(my_int<42>::value == 42, "my_int<42>::value != 42");

    return 0;
}

在这个示例代码中,我们定义了名为my_int的结构体,并继承了std::integral_constant<int, N>。接着,我们手动定义了一个类型别名value,以便访问编译时常量的值。
但是,在C++14中,我们不再需要手动定义类型别名,而是可以使用std::integral_constant提供的value_typetypeoperator()等成员函数来方便地访问常量的类型和值。例如:

#include <type_traits>

int main() {
    static_assert(std::integral_constant<int, 42>::value == 42, "std::integral_constant<int, 42>::value != 42");
    static_assert(std::is_same<std::integral_constant<int, 42>::value_type, int>::value, "wrong value_type");
    static_assert(std::is_same<std::integral_constant<int, 42>::type, std::integral_constant<int, 42>>::value, "wrong type");
    static_assert(std::integral_constant<int, 42>{}() == 42, "operator() failed");

    return 0;
}

在这个示例代码中,我们使用std::integral_constant<int, 42>来表示整数值42,并使用value_typetypeoperator()等成员函数来访问其类型和值。由于std::integral_constant提供了这些成员函数,因此可以更方便地使用编译时常量。
总之,在C++14中,标准库<utility>增强了std::integral_constant模板类,使其更易于使用,并提供了一些方便的成员函数来访问常量的类型和值。

2.8. User-defined literals for and

C++14引入了用户定义字面量(user-defined literals)的概念,允许程序员自定义字面量的解释方式。在C++14中,和库也引入了用户定义字面量的支持。
库中的用户定义字面量允许程序员使用自定义的时间单位,例如毫秒、微秒、纳秒等。例如:

using namespace std::chrono_literals;

auto duration = 100ms;  // 表示100毫秒的时间段
auto time_point = 1h + 30min + 15s;  // 表示1小时30分钟15秒的时间点

库中的用户定义字面量允许程序员使用自定义的字符串类型。例如:

using namespace std::string_literals;

auto str = "hello"s;  // 表示一个std::string类型的字符串"hello"

通过使用用户定义字面量,程序员可以更方便地编写代码,使代码更加易读易懂。但是需要注意的是,用户定义字面量可能会引入一定的性能开销,因此在使用时需要权衡利弊。在C++14中,标准库<iterator>增加了对空的“前向迭代器”(forward iterator)的支持。这使得我们可以创建表示“空范围”的迭代器,而无需实际引用任何元素。

2.9. Null forward iterators

例如,假设我们有一个需要接受范围作为参数的函数:

#include <iterator>

template<typename ForwardIt>
void print_range(ForwardIt first, ForwardIt last) {
    for (auto it = first; it != last; ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';
}

在这个函数中,我们使用了一对“前向迭代器”来表示范围,并打印其中的元素。
但是,在某些情况下,我们可能需要将一个空范围传递给该函数。在C++11中,这通常需要传递两个相同的迭代器或使用特殊值std::begin(x)std::end(x)来表示空范围。
在C++14中,我们可以直接使用std::nullptr_t来表示空的前向迭代器。例如:

#include <iterator>

template<typename ForwardIt>
void print_range(ForwardIt first, ForwardIt last) {
    for (auto it = first; it != last; ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';
}

int main() {
    int arr[] = {1, 2, 3};
    
    print_range(arr + 1, arr + 1); // an empty range
    print_range(nullptr, nullptr); // another empty range

    return 0;
}

在这个示例代码中,我们使用nullptr来表示两个不同的空范围,并将它们传递给print_range函数。由于std::nullptr_t可以隐式转换为任何指针类型,因此可以用作表示空范围的“前向迭代器”。
总之,在C++14中,标准库<iterator>增加了对空的“前向迭代器”的支持,我们可以使用std::nullptr_t来表示空范围,并直接传递给需要接受范围作为参数的函数。这使得代码更加简洁和易于理解。

2.10. std::quoted

在C++14中,标准库<iomanip>增加了一个名为std::quoted的函数模板,用于将字符串或字符序列包装在引号中。
例如,考虑以下代码:

#include <iostream>
#include <string>

int main() {
    std::string name = "John Smith";
    std::cout << "Hello, " << name << "!\n";
    
    return 0;
}

在这个示例代码中,我们使用std::string类型的变量name来存储一个名字,并在输出语句中直接使用它。然而,在某些情况下,我们需要将字符串包装在引号中,以便更明确地表示其内容。
在C++14中,我们可以使用std::quoted函数来轻松实现此目的。例如:

#include <iostream>
#include <iomanip>
#include <string>

int main() {
    std::string name = "John Smith";
    std::cout << "Hello, " << std::quoted(name) << "!\n";

    return 0;
}

在这个示例代码中,我们使用std::quoted函数将字符串name包装在引号中,并在输出语句中使用它。由于std::quoted返回一个std::basic_string<char>类型的对象,因此可以直接传递给输出流。
当我们运行这段代码时,输出将为:

Hello, "John Smith"!

总之,在C++14中,标准库<iomanip>提供了一个名为std::quoted的函数模板,用于将字符串或字符序列包装在引号中。这使得代码更加易于理解和维护。

2.11. std::make_unique

std::make_unique 是 C++14 中引入的一个新函数模板,用于创建一个动态分配的对象并返回一个指向该对象的 std::unique_ptr 智能指针。
std::make_unique 的语法如下:

template< class T, class... Args >
std::unique_ptr<T> make_unique( Args&&... args );

其中,T 是指向对象类型的指针类型,Args 是传递给构造函数的参数类型。make_unique 函数返回一个 std::unique_ptr 智能指针,该指针指向一个动态分配的 T 类型的对象。
使用 std::make_unique 可以避免手动调用 new 进行动态内存分配,从而减少内存泄漏和代码中出现的错误。例如:

std::unique_ptr<int> ptr = std::make_unique<int>(42);

上述代码将创建一个动态分配的 int 类型的对象,并返回一个指向该对象的 std::unique_ptr 智能指针。该智能指针将在作用域结束时自动释放分配的内存。
需要注意的是,std::make_unique 只能用于动态分配对象,不能用于动态分配数组。如果需要动态分配数组,应该使用 std::make_unique 的重载版本,或者使用 std::unique_ptr 的构造函数。

2.12. Heterogeneous associative lookup

在C++14中,标准库<utility><functional>增加了对混合类型键(heterogeneous key)的支持,以便更容易地使用关联容器存储不同类型的键。
例如,考虑以下代码:

#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    std::unordered_map<std::string, int> m = {{"one", 1}, {"two", 2}};
    
    const char* str = "one";
    if (m.find(str) != m.end()) {
        std::cout << "Found " << str << ": " << m[str] << "\n";
    } else {
        std::cout << "Not found: " << str << "\n";
    }
     
    return 0;
}

在这个示例代码中,我们使用std::unordered_map来存储字符串和整数之间的映射,并尝试在const char*类型的字符串"one"中查找相应的值。由于std::unordered_map的键类型为std::string,因此不能直接传递const char*类型的字符串,并且需要进行手动转换。这使得代码变得复杂和难以维护。
在C++14中,我们可以使用std::hash函数模板和std::equal_to函数对象模板来实现混合类型键的支持。例如:

#include <iostream>
#include <unordered_map>
#include <string>
#include <functional>

int main() {
    std::unordered_map<std::string, int> m = {{"one", 1}, {"two", 2}};

    const char* str = "one";
    if (auto it = m.find(str, std::hash<const char*>(), std::equal_to<>()); it != m.end()) {
        std::cout << "Found " << str << ": " << it->second << "\n";
    } else {
        std::cout << "Not found: " << str << "\n";
    }

    return 0;
}

在这个示例代码中,我们使用std::hash<const char*>()std::equal_to<>()作为第二个和第三个参数传递给m.find()函数,以表示键类型为const char*。由于C++14支持模板参数推导,因此可以省略掉std::equal_to<>的模板参数。
当我们运行这段代码时,输出将为:

Found one: 1

总之,在C++14中,标准库<utility><functional>增加了对混合类型键的支持,使得我们能够更容易地使用关联容器存储不同类型的键,并且使得代码更加简洁和易于维护。

2.13. std::integer_sequence

std::integer_sequence 是 C++14 中引入的一个模板类,用于表示一个整数序列。
std::integer_sequence 的定义如下:

template<typename T, T... Ints>
struct integer_sequence
{
    // ...
};

其中,T 是整数类型,Ints 是整数序列。integer_sequence 类模板是一个变长模板,可以接受任意个数的整数作为模板参数。
std::integer_sequence 通常与模板元编程一起使用,用于生成函数参数序列。例如,下面的代码展示了如何使用 std::integer_sequence 生成一个指定长度的函数参数序列:

template<typename F, typename... Args, std::size_t... Indices>
decltype(auto) apply_helper(F&& f, std::tuple<Args...>&& args, std::index_sequence<Indices...>)
{
    return std::invoke(std::forward<F>(f), std::get<Indices>(std::move(args))...);
}

template<typename F, typename... Args>
decltype(auto) apply(F&& f, std::tuple<Args...>&& args)
{
    return apply_helper(std::forward<F>(f), std::move(args), std::index_sequence_for<Args...>{});
}

上述代码中,std::index_sequence_for 返回一个 std::index_sequence 类型的对象,它包含了模板参数包 Args 的下标序列。然后,std::index_sequence 作为第三个参数传递给 apply_helper 函数,用于展开 std::tuple 中的元素并调用函数 f
需要注意的是,std::integer_sequence 在模板元编程中非常有用,但它的语法可能会比较复杂,需要一定的模板元编程知识才能使用。

2.14. std::shared_timed_mutex

在C++14中,标准库<shared_mutex>增加了一个名为std::shared_timed_mutex的类,它是一个可共享(shared)的互斥量,支持读写锁和超时锁等功能。
std::mutex类似,std::shared_timed_mutex也具有两种锁定方式:独占(exclusive)锁和共享(shared)锁。使用独占锁可以防止其他线程访问被保护的资源,而使用共享锁则允许多个线程同时读取该资源。
此外,std::shared_timed_mutex还支持超时锁,这意味着当尝试获得锁的操作未能在指定时间内完成时,将自动放弃该操作并返回错误代码。这在处理竞争条件时非常有用,因为它可以避免线程被死锁或挂起。
以下是一个简单的示例代码,演示如何使用std::shared_timed_mutex

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_timed_mutex mutex;
int data = 0;

void reader() {
    std::shared_lock<std::shared_timed_mutex> lock(mutex);
    std::cout << "Reader: " << data << "\n";
}

void writer() {
    std::unique_lock<std::shared_timed_mutex> lock(mutex);
    ++data;
    std::cout << "Writer: " << data << "\n";
}

int main() {
    std::thread t1(reader);
    std::thread t2(writer);
    std::thread t3(reader);
    t1.join();
    t2.join();
    t3.join();

    return 0;
}

在这个示例代码中,我们使用std::shared_timed_mutex来保护一个整数变量data。我们创建了三个线程,其中两个线程(t1t3)使用共享锁来读取该变量的值,而另一个线程(t2)使用独占锁来增加该变量的值。
由于std::shared_timed_mutex是一个可共享(shared)的互斥量,因此多个线程可以同时持有其共享锁,以读取共享资源。然而,当线程需要修改共享资源时,它必须使用独占锁,以防止其他线程同时访问该资源。
当我们运行这段代码时,输出将为:

Reader: 0
Writer: 1
Reader: 1

总之,在C++14中,标准库<shared_mutex>增加了一个名为std::shared_timed_mutex的类,使得我们能够更方便地实现读写锁和超时锁等功能,并且使得处理竞争条件更加简单和易于维护。

2.15. std::exchange

std::exchange 是 C++14 中引入的一个函数模板,用于交换一个变量的值,并返回原始值。
std::exchange 的语法如下:

template<class T, class U = T>
T exchange(T& obj, U&& new_value);

其中,obj 是要交换值的对象,new_value 是新的值。std::exchange 会将 obj 的值保存到一个临时变量中,并将 new_value 赋值给 obj,最后返回临时变量的值。
std::exchane 的作用是在不使用移动语义的情况下,将一个变量的值移动到另一个变量中。例如:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2;
std::exchange(v2, std::move(v1));  // 将 v1 移动到 v2 中

上述代码中,std::exchangev1 的值移动到 v2 中,并返回 v2 原来的值。这样可以避免使用 std::swap 或手动编写移动构造函数和移动赋值运算符的麻烦。std::exchange函数对于多线程编程非常有用,因为它可以在不需要锁定的情况下更新共享状态。
需要注意的是,std::exchange 可能会引入一定的性能开销,因此在使用时需要权衡利弊。

2.16. fixing constexpr member functions without const

在C++14之前,对于非const成员函数,即使其没有修改任何成员变量的值,也不能被声明为constexpr。这是由于C++11标准规定,constexpr函数必须是无副作用(side-effect-free)的,并且返回值必须仅依赖于其参数和模板参数。
然而,在C++14中,我们可以通过为非const成员函数添加const限定符来解决此问题。添加const限定符意味着该函数不会修改对象的状态,并且只能访问该对象的成员变量和其他const成员函数。因此,如果一个非const成员函数不改变对象的状态,我们可以将其声明为const,以使其满足constexpr函数的要求。
以下是一个简单的示例代码,展示如何使用const限定符来修复一个非const成员函数的constexpr声明:

class Foo {
public:
    constexpr Foo(int v) : value(v) {}
    constexpr int getValue() const { return value; }
private:
    int value = 0;
};

int main() {
    constexpr Foo foo{42};
    static_assert(foo.getValue() == 42, "");
    return 0;
}

在这个示例代码中,我们定义了一个类Foo,其中包含一个非const成员函数getValue(),该函数返回类成员变量value的值。由于getValue()函数没有改变对象的状态,因此我们可以在其声明中添加const限定符,使其满足constexpr函数的要求。
在main()函数中,我们创建了一个constexpr对象foo,并使用static_assert断言来验证getValue()函数的返回值是否正确。由于getValue()函数被声明为constexpr,因此我们可以在编译时检查该断言是否成立。
总之,在C++14中,我们可以通过添加const限定符来修复非const成员函数的constexpr声明,并使其能够用于constexpr上下文中。

2.17. std::get()

std::get<T>(tuple)是一个C++14标准库函数,用于从给定的tuple中获取指定类型T的元素。这个函数模板定义在<tuple>头文件中。
以下是std::get<T>(tuple)函数的通用模板定义:

template <std::size_t I, class... Types>
constexpr tuple_element_t<I, tuple<Types...>>& get(tuple<Types...>& t) noexcept;

template <std::size_t I, class... Types>
constexpr tuple_element_t<I, tuple<Types...>>&& get(tuple<Types...>&& t) noexcept;

template <std::size_t I, class... Types>
constexpr const tuple_element_t<I, tuple<Types...>>& get(const tuple<Types...>& t) noexcept;

template <std::size_t I, class... Types>
constexpr const tuple_element_t<I, tuple<Types...>>&& get(const tuple<Types...>&& t) noexcept;

其中,I表示将要访问的元素的索引(从0开始),Types...是存储在tuple中的类型列表。
这些函数使用了右值引用和完美转发,以便可以进行移动语义,并且可以避免出现不必要的复制操作。另外,由于这些函数都是constexpr的,因此它们可以在编译时执行,并且可以用于编写高性能的代码。
以下是一个简单的示例代码,展示如何使用std::get<T>(tuple)函数来访问tuple对象中的元素:

#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> myTuple(42, 3.14, "hello");
    int i = std::get<0>(myTuple);
    double d = std::get<1>(myTuple);
    std::string s = std::get<2>(myTuple);
    std::cout << i << ", " << d << ", " << s << std::endl;
    return 0;
}

在这个示例代码中,我们定义了一个包含三个元素的tuple对象myTuple,并使用std::get<T>(tuple)函数来访问其各个元素。特别地,我们使用std::get<0>(myTuple)访问第一个元素(整数类型),使用std::get<1>(myTuple)访问第二个元素(双精度浮点数类型),以及使用std::get<2>(myTuple)访问第三个元素(字符串类型)。
输出结果为:

42, 3.14, hello

总之,std::get<T>(tuple)是一个C++14标准库函数,可用于从tuple对象中获取指定类型的元素。它可以通过模板参数来确定要访问的元素的索引,并且可以使用右值引用和完美转发,以便进行移动语义并避免不必要的复制。

2.18. Dual-Range std::equal, std::is_permutation, std::mismatch

在C++14中,标准库中的std::equalstd::is_permutationstd::mismatch函数增加了对双区间(dual-range)的支持。
双区间是由两个序列组成的,通常是一个源序列和一个目标序列。它们可以有不同的长度,并且可以从任何位置开始比较。
以下是这些函数的新版本的模板定义:

template<class InputIterator1, class InputIterator2>
bool equal(InputIterator1 first1, InputIterator1 last1,
           InputIterator2 first2, InputIterator2 last2);

template<class InputIterator1, class InputIterator2>
bool equal(InputIterator1 first1, InputIterator1 last1,
           InputIterator2 first2, InputIterator2 last2,
           BinaryPredicate pred);

template<class ForwardIterator1, class ForwardIterator2>
bool is_permutation(ForwardIterator1 first1, ForwardIterator1 last1,
                    ForwardIterator2 first2, ForwardIterator2 last2);

template<class ForwardIterator1, class ForwardIterator2, class BinaryPredicate>
bool is_permutation(ForwardIterator1 first1, ForwardIterator1 last1,
                    ForwardIterator2 first2, ForwardIterator2 last2,
                    BinaryPredicate pred);

template<class InputIterator1, class InputIterator2>
pair<InputIterator1, InputIterator2> mismatch(InputIterator1 first1, InputIterator1 last1,
                                              InputIterator2 first2);

template<class InputIterator1, class InputIterator2>
pair<InputIterator1, InputIterator2> mismatch(InputIterator1 first1, InputIterator1 last1,
                                              InputIterator2 first2, InputIterator2 last2);

template<class InputIterator1, class InputIterator2, class BinaryPredicate>
pair<InputIterator1, InputIterator2> mismatch(InputIterator1 first1, InputIterator1 last1,
                                              InputIterator2 first2, BinaryPredicate pred);

template<class InputIterator1, class InputIterator2, class BinaryPredicate>
pair<InputIterator1, InputIterator2> mismatch(InputIterator1 first1, InputIterator1 last1,
                                              InputIterator2 first2, InputIterator2 last2,
                                              BinaryPredicate pred);

这些函数中的前两个参数指定了第一个双区间,而第三和第四个参数指定了第二个双区间(如果有的话)。equal函数比较两个序列是否相等。is_permutation函数比较两个序列是否拥有相同的元素,并且它们可以是以任何顺序出现的。mismatch函数在两个序列中找到第一个不匹配的元素。
这些函数还可以接受谓词函数对象作为可选的第五个参数,以便自定义元素之间的比较操作。
以下是一个使用新版本的equal函数的示例代码:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec1{1, 2, 3, 4, 5};
    std::vector<int> vec2{0, 0, 2, 4, 6};

    // compare two ranges for equality
    bool result = std::equal(vec1.begin(), vec1.end(), vec2.begin() + 2, vec2.end());
    std::cout << "The two ranges are " << (result ? "equal." : "not equal.") << std::endl;

    return 0;
}

在这个示例代码中,我们使用新版本的equal函数来比较两个不同长度的vector对象的子序列是否相等。具体地,我们比较vec1的前五个元素和vec2的后三个元素(即从位置2开始)是否相等。
输出结果为:

The two ranges are not equal.

总之,C++14标准库中的std::equalstd::is_permutationstd::mismatch函数增加了对双区间的支持,使得比较两个序列的操作更加灵活和通用。

2.19. type alias versions of type traits

在C++14中,标准库为许多类型特性(type traits)提供了类型别名版本,以便更容易地使用和理解这些特性。
以下是一些常见的类型特性及其类型别名的示例:

  • std::is_const<T>std::is_const_v<T>
  • std::is_pointer<T>std::is_pointer_v<T>
  • std::is_integral<T>std::is_integral_v<T>
  • std::is_floating_point<T>std::is_floating_point_v<T>
  • std::is_same<T, U>std::is_same_v<T, U>
  • std::enable_if<Cond, T>std::enable_if_t<Cond, T>

这些类型别名的命名约定是在原有类型特性名称后面加上_v,以表示其是一个值类型的常量表达式。这样,我们就可以在需要使用这些类型特性时,直接使用这些简单明了的类型别名,而无需记住模板类或成员常量的详细语法。
以下是一个使用类型别名版本的示例代码:

#include <iostream>
#include <type_traits>

template<typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
void printDouble(T t) {
    std::cout << static_cast<double>(t) << std::endl;
}

int main() {
    float f = 3.14f;
    double d = 2.71;

    printDouble(f);
    printDouble(d);

    return 0;
}

在这个示例代码中,我们使用std::is_floating_point_v<T>类型别名来判断传入的模板参数类型是否是浮点类型。如果是,则将其转换为double类型并输出。注意,我们使用std::enable_if_t<Cond, T>类型别名作为函数模板的默认模板参数,以便在编译时禁用该函数模板对非浮点类型的实例化。
输出结果为:

3.14
2.71

总之,C++14标准库为许多类型特性提供了类型别名版本,使得使用和理解这些特性更加方便和直观。我们可以通过在原有类型特性名称后面加上_v,来快速获取该类型特性的值类型常量表达式。这些类型别名在编写泛型代码时特别有用,因为它们可以使代码更简洁且易于理解。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值