C++14 语言

C++14 语言扩展

以下是 C++14 中 C++ 标准语言的主要新增和改进。除了此处列出的内容之外,还有各种较小的改进和错误修复,包括各种“透明”改进,例如“现在保证程序在您尚未注意到的极端情况下也能按预期运行”。

二进制字面量

C++ 现在支持二进制字面量

// the answer to life, the universe, etc. in...
auto a1 = 42;        // ... decimal
auto a2 = 0x2A;      // ... hexadecimal
auto a3 = 0b101010;  // ... binary

这与新的 ' 数字分隔符结合使用效果很好,例如用于分隔半字节或字节

auto a = 0b100'0001;  // ASCII 'A'

另请参见

泛型返回类型推导

C++11 允许自动推导仅包含单个 return 语句的 lambda 函数的返回类型

// C++11
[=]() -> some_type { return foo() * 42; } // ok
[=]                { return foo() * 42; } // ok, deduces "-> some_type"

这在两个方面得到了扩展。首先,它现在甚至适用于包含多个 return 语句的更复杂的函数体,只要所有 return 语句都返回相同的类型

// C++14
[=] {                                     // ok, deduces "-> some_type"
    while( something() ) {
        if( expr ) {
            return foo() * 42;            // with arbitrary control flow
        }
    }
    return bar.baz(84);                   // & multiple returns
}                                         //   (types must be the same)

其次,它现在适用于所有函数,而不仅仅是 lambda

// C++11, explicitly named return type
some_type f()         { return foo() * 42; } // ok
auto f() -> some_type { return foo() * 42; } // ok

// C++14
auto f()              { return foo() * 42; } // ok, deduces "-> some_type"

auto g() {                                // ok, deduces "-> some_type"
    while( something() ) {
        if( expr ) {
            return foo() * 42;            // with arbitrary control flow
        }
    }
    return bar.baz(84);                   // & multiple returns
}                                         //   (types must be the same)

当然,这需要函数体是可见的。

最后,有人会问:“嗯,这适用于递归函数吗?”答案是肯定的,只要 return 出现在递归调用之前。

另请参见

decltype(auto)

给定这些函数

string  lookup1();
string& lookup2();

在 C++11 中,我们可以编写以下包装函数,它们记住保留返回类型的引用性

string  look_up_a_string_1() { return lookup1(); }
string& look_up_a_string_2() { return lookup2(); }

在 C++14 中,我们可以自动化它

decltype(auto) look_up_a_string_1() { return lookup1(); }
decltype(auto) look_up_a_string_2() { return lookup2(); }

注意:decltype(auto) 主要用于推导转发函数和类似包装器的返回类型,如上所示,您希望类型精确地“跟踪”您正在调用的某个表达式。但是,decltype(auto) 并非旨在成为广泛使用的功能。特别是,尽管它可以用于声明局部变量,但这样做可能只是一个反模式,因为局部变量的引用性不应取决于初始化表达式。此外,它对您编写 return 语句的方式很敏感。这两个函数具有不同的返回类型

decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); }

第一个返回 string,第二个返回 string &,它是对局部变量 str 的引用。

另请参见

泛型 lambda 捕获

在 C++11 中,lambda 无法(轻易地)通过移动捕获。在 C++14 中,我们有了泛型 lambda 捕获,它不仅解决了这个问题,还允许您在 lambda 对象中定义任意新的局部变量。例如

auto u = make_unique<some_type>( some, parameters );  // a unique_ptr is move-only

go.run( [ u=move(u) ] { do_something_with( u ); } ); // move the unique_ptr into the lambda

在上面的示例中,我们将变量 u 的名称在 lambda 内部保持不变。但我们不限于此……我们可以重命名变量

go.run( [ u2=move(u) ] { do_something_with( u2 ); } ); // capture as "u2"

并且我们可以向 lambda 对象添加任意新状态,因为每次捕获都会在 lambda 内部创建一个新的类型推导局部变量

int x = 4;
int z = [&r = x, y = x+1] {
            r += 2;         // set x to 6; "R is for Renamed Ref"
            return y+2;     // return 7 to initialize z
        }(); // invoke lambda

另请参见

泛型 lambda

lambda 函数参数现在可以是 auto,让编译器推导类型。这将生成一个带模板化 operator() 的 lambda 类型,因此可以使用任何合适的类型调用同一个 lambda 对象,并会自动生成一个具有正确参数类型的类型安全函数。

在 C++11 中,我们必须明确声明 lambda 参数的类型,这通常没问题,但有时很烦人

// C++11: have to state the parameter type

for_each( begin(v), end(v), [](decltype(*cbegin(v)) x) { cout << x; } );

sort( begin(w), end(w), [](const shared_ptr<some_type>& a, 
                           const shared_ptr<some_type>& b) { return *a<*b; } );

auto size = [](const unordered_map<wstring, vector<string>>& m) { return m.size(); };

在 C++14 中,我们可以为与 C++11 中可以编写的相同函数获取类型推导

// C++14: just deduce the type

for_each( begin(v), end(v), [](const auto& x) { cout << x; } );

sort( begin(w), end(w), [](const auto& a, const auto& b) { return *a<*b; } );

除此之外,我们现在可以表达一些以前无法表达的新内容,即可以处理任何合适类型并正确执行的 lambda

// C++14: new expressive power

auto size = [](const auto& m) { return m.size(); };

请注意,此新版本的 size 不仅限于 unordered_map<wstring, vector<string>>,还可以使用任何具有 .size() 成员函数的类型调用。此外,由于它还隐式推导返回类型,因此返回类型将是 m.size() 返回的任何类型,这对于不同类型可能不同。

另请参见

变量模板

在 C++11 中,添加 using 类型别名和 constexpr 函数在很大程度上取代了对“特性”模板的需求。如果您想计算类型,则更喜欢使用模板类型别名 alias_t<T> 而不是 traits<T>::type,如果您想计算值,则更喜欢使用 constexprvalue_v(); 函数而不是 traits<T>::value

到目前为止,一切顺利。但事实证明,有时我们最终创建 constexpr 函数只是为了返回一个常量,而且由于我们可以将函数模板化,我们可以将常量“强制转换”为正确的类型。但是该函数只存在,因为我们无法直接表达模板化变量。

引入变量模板

// math constant with precision dictated by actual type
template<typename T> constexpr T pi = T(3.14159265358979323846);

// Example use:
template<class T> T area_of_circle_with_radius(T r) { return pi<T> * r * r; }

// Same use, in a  more C++14-stylish way:
auto area_of_circle_with_radius = [](auto r) { return pi<decltype(r)> * r * r; };

另请参见

扩展 constexpr

在 C++11 中,将函数设为 constexpr 可能意味着重写它。例如,假设我们有这个 constexpr 函数

constexpr int my_charcmp( char c1, char c2 ) {
    return (c1 == c2) ? 0 : (c1 < c2) ? : -1 : 1;
}

这对于字符来说很好用,为什么不将其扩展到字符串呢?这将需要遍历字符串的字符,而 C++11 在 constexpr 函数中不允许这样做,因此支持字符串的 C++11 版本必须是递归的(并且更复杂一些)。

C++14 现在允许在 constexpr 函数体内执行更多操作,尤其是

  • 局部变量声明(不是 staticthread_local,也没有未初始化的变量)
  • 在常量表达式求值开始时生命周期开始的可变对象
  • ifswitchforwhiledo-while(不是 goto

因此,在 C++14 中,上述泛化到字符串的函数可以保持习惯用法,并直接使用普通循环

constexpr int my_strcmp( const char* str1, const char* str2 ) {
    int i = 0;
    for( ; str1[i] && str2[i] && str1[i] == str2[i]; ++i )
        { }
    if( str1[i] == str2[i] ) return 0;
    if( str1[i] < str2[i] ) return -1;
    return 1;
}

C++14 还删除了 C++11 中 constexpr 成员函数隐式为 const 的规则。

另请参见

[ [deprecated] ] 属性

deprecated 属性允许将实体标记为已弃用,这意味着它仍然可以合法使用,但会提醒用户不鼓励使用,并且在编译期间可能会打印警告消息。

该属性可以应用于类、typedef 名称、变量、非静态数据成员、函数、枚举或模板特化声明。

另请参见

数字分隔符

单引号字符 ' 现在可以在数字字面量中的任何位置使用,以提高美观可读性。它不影响数字值。

auto million = 1'000'000;
auto pi = 3.14159'26535'89793;

另请参见