C++11 语言扩展 — 其他语言特性
C++11 中 __cplusplus
的值是什么?
在 C++11 中,宏 __cplusplus
的值被设置为 201103L
。(在 C++11 之前,它是 199711L
。)
尾随返回类型语法
考虑
template<class T, class U>
??? mul(T x, U y)
{
return x*y;
}
我们可以将什么作为返回类型?当然是“x*y
的类型”,但是我们如何表达它呢?第一个想法是使用 decltype
template<class T, class U>
decltype(x*y) mul(T x, U y) // scope problem!
{
return x*y;
}
这不起作用,因为 x
和 y
不在作用域内。然而,我们可以这样写
template<class T, class U>
decltype(*(T*)(0)**(U*)(0)) mul(T x, U y) // ugly! and error prone
{
return x*y;
}
然而,称其为“不太美观”就过于客气了。
解决方案是将返回类型放在它所属的位置,即参数之后
template<class T, class U>
auto mul(T x, U y) -> decltype(x*y)
{
return x*y;
}
我们使用 auto
符号来表示“返回类型待推导或稍后指定”。
尾随语法并非主要关于模板和类型推导,它实际上是关于作用域。
struct List {
struct Link { /* ... */ };
Link* erase(Link* p); // remove p and return the link before p
// ...
};
List::Link* List::erase(Link* p) { /* ... */ }
第一个 List::
仅在进入第二个 List::
的作用域之前才需要。更好的做法是
auto List::erase(Link* p) -> Link* { /* ... */ }
现在,Link
都不需要显式限定。
另请参见
- [Str02] Bjarne Stroustrup。《关于 "typeof" 的提案草案》。C++ reflector 消息 c++std-ext-5364,2002 年 10 月。
- [N1478=03-0061] Jaakko Jarvi、Bjarne Stroustrup、Douglas Gregor 和 Jeremy Siek:《Decltype 和 auto》。
- [N2445=07-0315] Jason Merrill:《新函数声明符语法措辞》。
- [N2825=09-0015] Lawrence Crowl 和 Alisdair Meredith:《统一函数语法》。
阻止缩窄转换
问题:C 和 C++ 隐式截断
int x = 7.3; // Ouch!
void f(int);
f(7.3); // Ouch!
然而,在 C++11 中,{}
初始化不会发生缩窄转换
int x0 {7.3}; // error: narrowing
int x1 = {7.3}; // error: narrowing
double d = 7;
int x2{d}; // error: narrowing (double to int)
char x3{7}; // ok: even though 7 is an int, this is not narrowing
vector<int> vi = { 1, 2.3, 4, 5.6 }; // error: double to int narrowing
C++11 避免许多不兼容性的方式是,在决定什么是缩窄转换时,如果可能(而不仅仅是类型),它会依赖于初始化器的实际值(例如上面示例中的 7
)。如果一个值可以精确地表示为目标类型,则该转换不是缩窄转换。
char c1{7}; // OK: 7 is an int, but it fits in a char
char c2{77777}; // error: narrowing (assuming 8-bit chars)
请注意,浮点数到整数的转换始终被视为缩窄转换——即使是 7.0
到 7
也是如此。
另请参见
- C++ 草案 8.5.4 节。
- [N1890=05-0150] Bjarne Stroustrup 和 Gabriel Dos Reis: 初始化和初始化器(初始化相关问题的概述以及建议的解决方案)。
- [N2215=07-0075] Bjarne Stroustrup 和 Gabriel Dos Reis:《初始化列表(修订版 3)》。
- [N2640=08-0150] Jason Merrill 和 Daveed Vandevoorde:《初始化列表 – 替代机制和基本原理(v. 2)》(最终提案)。
右尖括号
考虑
list<vector<string>> lvs;
在 C++98 中,这会是一个语法错误,因为两个 >
之间没有空格。C++11 识别这样的两个 >
为两个模板参数列表的正确终止符。
为什么这曾经是一个问题?编译器前端是按解析/阶段组织的。这是最简单的模型
- 词法分析(从字符组成 token)
- 语法分析(检查语法)
- 类型检查(查找名称和表达式的类型)
这些阶段在理论上和实践中有时是严格分离的,因此确定 >>
是一个 token(通常表示右移或输入)的词法分析器对其含义一无所知;特别是,它对模板或嵌套模板参数列表一无所知。然而,为了使该示例“正确”,这三个阶段必须以某种方式协同工作。导致问题解决的关键观察是,每个 C++ 编译器都已经理解了这个问题,因此它可以给出体面的错误消息。
另请参见
- [N1757==05-0017] Daveed Vandevoorde:《修订后的右尖括号提案(修订版 2)》。
static_assert
编译时断言
静态(编译时)断言由一个常量表达式和一个字符串字面量组成
static_assert(expression,string);
如果表达式为假(即断言失败),编译器会评估表达式并将字符串作为错误消息写入。例如
static_assert(sizeof(long)>=8, "64-bit code generation required for this library.");
struct S { X m1; Y m2; };
static_assert(sizeof(S)==sizeof(X)+sizeof(Y),"unexpected padding in S");
static_assert
可用于使程序及其由编译器处理的假设变得明确。请注意,由于 static_assert
在编译时进行评估,因此它不能用于检查依赖于运行时值的假设。例如
int f(int* p, int n)
{
static_assert(p==0,"p is not null"); // error: static_assert() expression not a constant expression
// ...
}
(相反,使用普通的 assert(p==0 && "p is not null");
或在失败时进行测试并抛出异常。)
另请参见
- C++ 草案 7 [4]。
- [N1381==02-0039] Robert Klarer 和 John Maddock:《将静态断言添加到核心语言的提案》。
- [N1720==04-0160] Robert Klarer、John Maddock、Beman Dawes、Howard Hinnant:《将静态断言添加到核心语言的提案(修订版 3)》。
原始字符串字面量
在许多情况下,例如当您为与标准 regex
库一起使用而编写正则表达式时,反斜杠 (\
) 是转义字符这一事实是一个真正的麻烦,因为在正则表达式中,反斜杠用于引入表示字符类的特殊字符。考虑如何编写表示由反斜杠分隔的两个单词的模式 (\w\\\w
)
string s = "\\w\\\\\\w"; // I hope I got that right
请注意,反斜杠字符在正则表达式中表示为两个反斜杠。基本上,“原始字符串字面量”是一个字符串字面量,其中反斜杠只是一个反斜杠,因此我们的示例变为
string s = R"(\w\\\w)"; // I'm pretty sure I got that right
原始字符串的原始提案将其作为激励示例
"('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|" // Are the five backslashes correct or not?
// Even experts become easily confused.
R"(...)"
符号比“普通”的 "..."
冗长一些,但是当没有转义字符时,“更多”是必要的:如何将引号放入原始字符串?很简单,除非它前面有 )
R"("quoted string")" // the string is "quoted string"
那么,我们如何将字符序列 )"
放入原始字符串呢?幸运的是,这是一个罕见的问题,但是 "(...)"
只是默认的分隔符对。我们可以在 "(...)"
的 (...)
之前和之后添加分隔符。例如
R"***("quoted string containing the usual terminator (")")***" // the string is "quoted string containing the usual terminator (")"
)
之后的字符序列必须与 (
之前的序列相同。这样我们就可以处理(几乎)任意复杂的模式。
原始字符串的初始 R
之前可以带有编码前缀:u8
、u
、U
或 L
。例如,u8R"(fdfdfa)"
是一个 UTF-8 字符串字面量。
另请参见
- 标准 2.13.4
- [N2053=06-0123] Beman Dawes:《原始字符串字面量》(原始提案)。
- [N2442=07-0312] Lawrence Crowl 和 Beman Dawes:《原始和 Unicode 字符串字面量;统一提案(修订版 2)》(与 用户定义字面量提案 结合的最终提案)。
- [N3077==10-0067] Jason Merrill:《原始字符串问题的替代方法》(将
[
替换为(
);
属性
“属性”是一种新的标准语法,旨在为将可选和/或供应商特定信息添加到源代码(例如 __attribute__
、__declspec
和 #pragma
)的设施混乱提供一些秩序。C++11 属性与现有语法的区别在于,它们基本上适用于代码中的任何地方,并且始终与紧邻的语法实体相关。例如
void f [ [ noreturn ] ] () // f() will never return
{
throw "error"; // OK
}
struct foo* f [ [ carries_dependency ] ] (int i); // hint to optimizer
int* g(int* x, int* y [ [ carries_dependency ] ] );
如您所见,属性被放置在双括号内:[
[
… ]
]
。noreturn
和 carries_dependency
是标准中定义的两个属性。
人们有理由担心属性会被用来创建语言方言。建议仅使用属性来控制不影响程序含义但可能有助于检测错误(例如 noreturn
)或帮助优化器(例如 carries_dependency
)的事物。
属性的一个计划用途是改进对 OpenMP 的支持。例如
for [ [ omp::parallel() ] ] (int i=0; i<v.size(); ++i) {
// ...
}
(请注意,这个例子再次说明了对属性将被(滥)用于隐藏伪装成 [[关键字]] 的语言扩展的担忧……并行循环的语义与顺序循环的语义绝对不同。)
如所示,属性可以被限定。
另请参见
- 标准:7.6.1 属性语法和语义,7.6.3-4 noreturn,carries_dependency 8 声明符,9 类,10 派生类,12.3.2 转换函数
- [N2418=07-027] Jens Maurer, Michael Wong:《C++ 属性支持之路(修订版 3)》
对齐
偶尔,特别是当我们编写操作原始内存的代码时,我们需要为某些分配指定所需的对齐方式。例如
alignas(double) unsigned char c[1024]; // array of characters, suitably aligned for doubles
alignas(16) char[100]; // align on 16 byte boundary
还有一个 alignof
运算符,它返回其参数(必须是类型)的对齐方式。例如
constexpr int n = alignof(int); // ints are aligned on n byte boundaries
另请参见
- 标准:5.3.6 Alignof [expr.alignof]
- 标准:7.6.2 对齐说明符 [dcl.align]
- [N3093==10-0083] Lawrence Crowl:《C 和 C++ 对齐兼容性》。将提案与 C 的后续提案对齐。
- [N1877==05-0137] Attila (Farkas) Fehér:《在 C++ 编程语言中添加对齐支持。》原始提案。
C99 特性
为了保持高度兼容性,与 C 标准委员会合作引入了一些小的语言更改
long long
.- 扩展整数类型(即可选更长的
int
类型的规则)。 - UCN 更改 [N2170==07-0030] “取消字符和字符串字面量中控制字符和基本源通用字符名的限制。”
- 窄/宽字符串的连接。
- 不是 VLAs(可变长度数组),其更好的版本正在考虑标准化。
添加了一些预处理规则的扩展
__func__
一个扩展为词法当前函数名称的宏__STDC_HOSTED__
_Pragma
:_Pragma( X )
扩展为#pragma X
- 变参宏(具有不同参数数量的宏重载),例如
#define report(test, ...) ((test)?puts(#test):printf(_ _VA_ARGS_ _))
- 空宏参数
许多标准库设施继承自 C99(基本上所有 C99 库相对于其 C89 前身的更改)
参见
- 标准:16.3 宏替换。
- [N1568=04-0008] P.J. Plauger:《提议向 TR-1 添加内容以提高与 C99 的兼容性》。