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'
另请参见
- [N3472] James Dennett: C++ 核心语言中的二进制字面量。
泛型返回类型推导
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
出现在递归调用之前。
另请参见
- [N3638] Jason Merrill: 普通函数的返回类型推导。
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
的引用。
另请参见
- [N3638] Jason Merrill: 普通函数的返回类型推导。
泛型 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
另请参见
- [N3648] Daveed Vandevoorde, Ville Voutilainen: 泛型 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()
返回的任何类型,这对于不同类型可能不同。
另请参见
- [N3559] Faisal Vali, Herb Sutter, Dave Abrahams: 泛型(多态)Lambda 表达式的提议。
- [N3649] Faisal Vali, Herb Sutter, Dave Abrahams: 泛型(多态)Lambda 表达式(修订版 3)。
变量模板
在 C++11 中,添加 using
类型别名和 constexpr
函数在很大程度上取代了对“特性”模板的需求。如果您想计算类型,则更喜欢使用模板类型别名 alias_t<T>
而不是 traits<T>::type
,如果您想计算值,则更喜欢使用 constexpr
的 value_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; };
另请参见
- [N3651] Gabriel Dos Reis: 变量模板(修订版 1)。
扩展 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
函数体内执行更多操作,尤其是
- 局部变量声明(不是
static
或thread_local
,也没有未初始化的变量) - 在常量表达式求值开始时生命周期开始的可变对象
if
、switch
、for
、while
、do
-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
的规则。
另请参见
- [N3652] Richard Smith: 放宽对 constexpr 函数的限制。
[
[deprecated]
]
属性
deprecated
属性允许将实体标记为已弃用,这意味着它仍然可以合法使用,但会提醒用户不鼓励使用,并且在编译期间可能会打印警告消息。
该属性可以应用于类、typedef
名称、变量、非静态数据成员、函数、枚举或模板特化声明。
另请参见
- [N3760] Alberto Ganesh Barbati:
[
[deprecated]
]
属性。
数字分隔符
单引号字符 '
现在可以在数字字面量中的任何位置使用,以提高美观可读性。它不影响数字值。
auto million = 1'000'000;
auto pi = 3.14159'26535'89793;
另请参见
- [N3781] Lawrence Crowl, Richard Smith, Jeff Snyder, Daveed Vandevoorde: 单引号作为数字分隔符。