cpp11 语言 杂项

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;
    }

这不起作用,因为 xy 不在作用域内。然而,我们可以这样写

    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.07 也是如此。

另请参见

右尖括号

考虑

    list<vector<string>> lvs;

在 C++98 中,这会是一个语法错误,因为两个 > 之间没有空格。C++11 识别这样的两个 > 为两个模板参数列表的正确终止符。

为什么这曾经是一个问题?编译器前端是按解析/阶段组织的。这是最简单的模型

  • 词法分析(从字符组成 token)
  • 语法分析(检查语法)
  • 类型检查(查找名称和表达式的类型)

这些阶段在理论上和实践中有时是严格分离的,因此确定 >> 是一个 token(通常表示右移或输入)的词法分析器对其含义一无所知;特别是,它对模板或嵌套模板参数列表一无所知。然而,为了使该示例“正确”,这三个阶段必须以某种方式协同工作。导致问题解决的关键观察是,每个 C++ 编译器都已经理解了这个问题,因此它可以给出体面的错误消息。

另请参见

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"); 或在失败时进行测试并抛出异常。)

另请参见

原始字符串字面量

在许多情况下,例如当您为与标准 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 之前可以带有编码前缀:u8uUL。例如,u8R"(fdfdfa)" 是一个 UTF-8 字符串字面量。

另请参见

属性

“属性”是一种新的标准语法,旨在为将可选和/或供应商特定信息添加到源代码(例如 __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 ] ] );

如您所见,属性被放置在双括号内:[ [] ]noreturncarries_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

另请参见

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 前身的更改)

参见