ISO/IEC 14882:2011之条款5.1——基本表达式

5.1 基本表达式


5.1.1 通用


primary-expression:

    literal

    this

    ( expression )

    id-expression

    lambda-expression


id-expression:

    unqualified-id

    qualified-id


unqualified-id:

    identifier

    operator-function-id

    conversion-function-id

    literal-operator-id

    ~ class-name

    ~ decltype-specifier

    template-id


1、一个字面量是一个基本表达式。其类型依赖于其形式(2.14)。一个字符串字面量是一个左值;其它所有字面量都是纯右值。


2、关键字this命名了一个指向对象的指针,用于调用一个非静态成员函数或计算一个非静态数据成员的初始化器(9.2)。


3、如果一个声明声明了一个类X的一个成员函数或成员函数模板,那么表达式this是在可选的cv-qualifier-seqfunction-definitionmember-declarator、或declarator的结尾之间的“指向cv-qualifier-seq X的指针”类型的一个纯右值。它不应该出现在可选的cv-qualifier-seq之前并且它不应该出现在一个静态成员函数的声明内(尽管其类型和值类别被定义在一个静态成员函数内,由于它们在一个非静态成员函数内)。[注:这是因为声明匹配直到完整的声明符已知后才发生。——注结束]不像在其它上下文中的对象表达式,*this对于在成员函数体外的类成员访问(5.2.5)目的不要求是完整的类型。[注:只有在声明之前被声明的类成员是可见的。——注结束][例:

struct A {
    char g();
    template<class T> auto f(T t) -> decltype(t + g())
    { return t + g(); }
};
template auto A::f(int t) -> decltype(t + g());

——例结束]


4、否则,如果一个member-declarator声明了一个类X的一个非静态数据成员(9.2),表达式this是在可选的brace-or-equal-initializer内的“指向X的指针”类型的一个纯右值。它不应该出现在member-declarator中的其它地方。


5、表达式this不应该出现在任何其它上下文中。[例:

class Outer {
    int a[sizeof(*this)];                     // 错误:不在一个成员函数内
    unsigned int sz = sizeof(*this);          // OK:在brace-or-equal-initializer内

    void f() {
        int b[sizeof(*this)];                 // OK

        struct Inner {
            int c[sizeof(*this)];             // 错误:不在Inner的一个成员函数中
        };
    }
};

——例结束]


6、加圆括号的表达式是一个基本表达式,其类型与值跟封闭在括号内的表达式的相同。括号的出现并不影响该表达式是否为一个左值。加括号的表达式可以用在与内部封闭的表达式所可以使用的完全相同的上下文中,除非有其它说明。


7、一个id-expression是一个primary-expression的一种受限形式。[注:一个id-expression可以出现在 . 和 -> 操作符(5.2.5)之后——注结束]


8、一个identifier是一个id-expression,它通过适当地被声明来给出(条款7)。[注:对于operator-fucntion-id,见13.5;对于conversion-function-id,见12.3.2;对于literal-operator-id,见13.5.8;对于template-id,见14.2。由前缀 ~ 后所跟的一个class-namedecltype-specifier表示为一个析构器;见12.4。在一个非静态成员函数的定义内,命名一个非静态成员的一个identifier被变换为一个类成员访问表达式(9.3.1)——注结束]该表达式的类型是identifier的类型。该结果是由此标识符所表示的实体。该结果是一个左值,如果该实体是一个函数、变量或数据成员,而其它情况下是一个纯右值。

qualified-id:

    nested-name-specifier templateopt unqualified-id

    :: identifier

    :: operator-function-id

    :: literal-operator-id

    :: template-id


nested-name-specifier:

    ::opt type-name ::

    ::opt namespace-name ::

    decltype-specifier ::

    nested-name-specifier identifier ::

    nested-name-specifier templateopt simple-template-id ::


表示一个类的nested-name-specifier,可选地,后面跟关键字template,然后后面再跟该类的或其中一个其基类(条款10)的一个成员的名字,是一个qualified-id;3.4.3.1描述了出现在qualified-id中的类成员的名字查找。该结果是成员。结果的类型是该成员的类型。如果该成员是一个静态成员函数或一个数据成员,那么该结果是一个左值,否则是一个纯右值。[注:一个类成员可以在其潜在作用域(3.3.7)中的任一点,使用一个qualified-id来被引用。——注结束]在class-name :: class-name被使用的地方,并且两个class-name引用同一个类,那么这个标记法命名了一个构造器(12.1)。在class-name ::~ class-name被使用的地方,这里两个class-name将引用同一个类;这个标记法命名了析构器(12.4)。~decltype-specifier这种形式也表示了析构器,但它不应该作为一个qualified-id中的unqualified-id而被使用。[注:命名一个类的一个typedef-name是一个class-name(9.1)。[译者注:比如,

#include <iostream>
using namespace std;

class Test
{
public:
    
    ~Test(void)
    {
        cout << "Test is destroyed!" << endl;
    }
};

typedef Test TypeTest;

int main (int argc, const char * argv[])
{      
    TypeTest t;
    t.~TypeTest();
}

]——注结束]


9、一个 :: 或命名一个名字空间的一个nested-name-specifier ,在任一种情况下,后面跟那个名字空间的一个成员的名字(或通过一个using-directive使得一个名字空间可见的其一个成员的一个名字)是一个qualified-id;3.4.3.2描述了对出现在qualified-id中的名字空间成员的名字查找。结果是该成员。结果的类型是该成员的类型。如果该成员是一个函数或变量,那么此结果是一个左值,否则它是一个纯右值。


10、指示一个枚举(7.2)的一个nested-name-specifier,后面跟着那个枚举的一个枚举符的名字,是一个引用此枚举符的qualified-id。结果是该枚举符。结果的类型是该枚举的类型。结果是一个纯右值。


11、在一个qualified-id中,如果unqualified-id是一个conversion-function-id,那么其conversion-type-id应该在两个上下文中都应该表示相同的类型,这两个上下文是,整个qualified-id所发生在的上下文,以及由nested-name-specifier所指示的类的上下文。


12、指示一个类的非静态数据成员或非静态成员函数的一个id-expression只能被用于:

    ——作为一个类成员访问的一部分,其中,对象表达式引用成员的类[注:这也应用于当当对象表达式是一个隐式的(*this)时(9.3.1)],或派生自那个类的一个类,或

    ——形成一个指向成员的指针,或

    ——在那个类的或派生自那个类的一个类的一个构造器的一个mem-initializer中,或

    ——在那个类或派生自那个类的一个非静态数据成员的brace-or-equal-initializer中(12.6.2),或

    ——如果那个id-expression指示一个非静态数据成员,并且它在一个未被计算的操作数中出现。[例:

struct S {
    int m;
};
int i = sizeof(S::m);        // OK
int j = sizeof(S::m + 42);   // OK

——例结束]


5.1.2 Lambda表达式


1、Lambda表达式提供了一种简明的方法来创建简单的函数对象。[例:

#include <algorithm>
#include <cmath>
void abssort(float *f, unsigned N) {
    std::sort(x, x + N, 
    [](float a, float b) {
        return std::abs(a) < std::abs(b);
    });
}

——例结束]

    lambda-expression:

         lambda-introducer lambda-declaratoropt compound-statement

    lambda-introducer:

        [ lambda-captureopt ]

    lambda-capture:

        capture-default

        capture-list

        capture-default , capture-list

    capture-default:

        &

        =

    capture-list:

        capture ...opt

        capture-list , capture ...opt

    capture:

        identifier

        & identifier

        this

    lambda-declarator:

        ( parameter-declaration-clause ) mutableopt  exception-specificationopt  attribute-specifier-seqopt

                trailing-return-typeopt


2、一个lambda-expression的计算生成一个纯右值临时变量(12.2)。这个临时变量被称为闭包对象。一个lambda-expression不应该出现在一个不被计算的操作数(条款5)中。[注:一个闭包对象跟一个函数对象的行为一样。——注结束]


3、lambda-expression的类型(其也是闭包对象的类型)是一个唯一的,未命名的非联合类类型——称为闭包类型——其性质如下描述。这个类类型并不是一个聚合(8.5.1)。该闭包类型在含有相应lambda-expression的最小语句块作用域、类作用域、或名字空间作用域内被声明。[注:这决定了与闭包类型相关联的名字空间和类的集合(3.4.2)。一个lambda-declarator的形参类型并不影响这些相关联的名字空间和类。——注结束]一个实现可以定义与下述描述所不同的闭包类型,提供此描述并不更改程序的行为,不同于通过改变:

    ——闭包类型的大小以及/或者对齐,

    ——闭包类型是否为平凡可拷贝的(条款9),

    ——闭包类型是否为一个标准布局的类(条款9),或

    ——闭包类型是否为一个POD[译者注:Plain Old Data,朴素旧数据类型]类(条款9)。

一个实现不应该将右值引用类型的成员添加到闭包类型中。


4、如果一个lambda-expression不包含一个lambda-declarator,那么它就好比lambda-declarator是( )。如果一个lambda-expression不包含一个trailing-return-type,那么它就好比trailing-return-type指示以下类型:

    ——如果compound-statement是以下形式:

        { attribute-specifier-seqopt return expression ; }

        在左值到右值转换(4.1),数组到指针的转换(4.2),和函数到指针的转换(4.3)之后的所返回的表达式的类型;

    ——否则,为void

[例:

auto x1 = [](int i){ return i; };    // OK: 返回类型是int
auto x2 = []{ return { 1, 2 }; };    // 错误:返回类型是void,(一个braced-init-list并不是一个表达式)

——例结束]


5、一个lambda-expression的闭包类型具有一个公有内联函数调用操作符(13.5.4),其形参和返回类型分别由lambda-expressionparameter-declaration-clause以及trailing-return-type所描述。该函数调用操作符被声明为const(9.3.1),当且仅当lambda-expressionparameter-declaration-clause后面不跟mutable。它既不是虚拟的也不被声明为volatile。默认的实参(8.3.6)不应该在一个lambda-declaratorparameter-declaration-clause中指定。任一在lambda-expression上所指定的exception-specification应用于相应的函数调用操作符。在一个lambda-declarator中的attribute-specifier-seq属于相应的函数调用操作符的类型。[注:在lambda-declarator中所引用的名字在lambda-expression所出现的上下文中查找——注结束]


6、不带有lambda-capture的一个lambda-expression的闭包类型具有一个共有的非虚拟的非显式的常量的函数到函数指针的转换,具有与闭包类型的函数调用操作符相同的形参和返回类型。由这个转换函数所返回的值应该是一个函数的地址,当被调用时,具有与调用闭包类型的函数调用操作符相同的效果。


7、lambda-expressioncompound-statement引出函数调用操作符的function-body(8.4),但是对于名字查找目的,使用(*this)判定this的类型和值,并把引用非静态类型成员的id-expression变换为类成员访问表达式(9.3.1),compound-statement在lambda-expression的上下文中被考虑。【例:

struct S1 {
    int x, y;
    int operator(int);
    void f() {
        [=]()->int {
            return operator(this->x + y);    // 等价于S1::operator()(this->x + (*this).y
                                             // this具有S1*
        };
    }
};

 ——例结束】


8、如果一个lambda-capture包含了为 & 的一个capture-default,那么在lambda-capture中的标识符不应该前面再加 & 。如果一个lambda-capture包含了为 = 的一个capture-default,那么lambda-capture不应该包含this,并且它所包含的每个标识符的前面应该加 & 。一个标识符或this在一个lambda-capture中不应该出现多次。[例:

struct S2 { void f(int i); };
void S2::f(int i) {
    [&, i]{ };        // OK
    [&, &i]{ };      // 错误:在&作为默认时,i前面加了&
    [=, this]{ };   // 错误:当=为默认时,使用了this
    [i, i]{ };         // 错误:i被重复
} 

——例结束]


9、其最小封闭作用域是一个语句块作用域的一个lambda-expression是一个局部lambda表达式;任何其它lambda-expression在其lambda-introducer中不应该具有一个capture-list。一个局部lambda表达式的到达作用域是达到或包含最里部封闭函数及其形参的封闭作用域的集合。[注:此到达作用域包含任一中间的lambda-expression。——注结束]


10、在一个capture-list中的identifier使用通常的非限定名字查找的规则进行查找(3.4.1);每个这样的查找应该找到声明在局部lambda表达式的到达作用域中的带有自动存储周期的一个变量。一个实体(即一个变量或this)被称为是被显式捕获的,如果它出现在lambda-expressioncapture-list中。


11、如果一个lambda-expression具有一个相关联的capture-default,并且其compound-statementthis或一个带有自动存储周期的变量使用了一次定义规则(3.2)并且使用了一次定义规则的实体不被显式捕获,那么使用了一次定义规则实体被称为是被隐式地捕获;这样的实体应该在Lambda表达式的到达作用域内被声明。[注:由一个嵌套的lambda-expression对一个实体的隐式捕获会导致被包含lambda-expression的其隐式捕获(见下面)。对this的隐式的使用一次定义规则会导致隐式捕获。——注结束]


12、一个实体被捕获,如果它被隐式或显式地捕获。被一个lambda-expression所捕获的一个实体在包含lambda-expression的作用域中是使用一次定义规则的。如果this被一个局部lambda表达式所捕获,那么其最近的封闭函数应该是一个非静态成员函数。如果一个lambda-expressionthis或带有自动存储周期的一个变量从其到达作用域使用了一次定义规则,那么那个实体应该被lambda-expression所捕获。如果一个lambda-expression捕获了一个实体,并且那个实体没有在封闭的lambda表达式或函数中被立即定义或捕获,那么该程序是不良形式的。[例:

void f1(int i) {
    int const N = 20;
    auto m1 = [=]{
        int const M = 30;
        auto m2 = [i]{
            int x[N][M];        // OK:N和M没有使用一次定义规则
            x[0][0] = i;        // OK:i被m2所显式捕获并被m1所隐式捕获
        };
    };

    struct S1 {
        int f;
        void work(int n){
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]{    // 错误:j不被m3所捕获
                auto m4 = [&, j]{   // 错误:n被m4所隐式捕获,但不被m3所捕获
                    x += m;         // OK:m被m4所隐式捕获并且被m3所显式捕获
                    x += i;         // 错误:i在到达作用域的外部
                    x += f;         // OK:this被m4隐式捕获,并被m3显式捕获
                };
            };
         }
    };             
}

——例结束] 


13、在一个默认实参中出现的一个lambda-expression,不应该显式地或隐式地捕获任一实体。[例:

void f2() {
    int i = 1;
    void g1(int = ([i]{ return i; })());    // 不良形式的
    void g2(int = ([i]{ return 0; }());    // 不良形式的
    void g3(int = ([i]{ return i; })());    // 不良形式的
    void g4(int = (int = ([=]{ return 0; })());    // OK
    void g5(int = ([]{ return sizeof i; })());        // OK
}

——例结束]


14、一个实体被拷贝捕获,如果它被隐式捕获并且capture-default= 或用一个不包含一个 & 的捕获来显式地捕获它。对于每个用拷贝来捕获的实体,在闭包类型中声明了一个未命名的静态数据成员。这些成员的声明次序不被指定。如果该实体不是对一个对象的引用,那么这样一个数据成员的类型是相应被捕获的实体的类型,否则,则为被引用的类型。[注:如果被捕获的实体是对一个函数的引用,那么相应的数据成员也是对一个函数的引用。——注结束]


15、如果一个实体被隐式或显式地捕获,但没有被拷贝捕获,那么该实体被引用捕获。在为由引用所捕获的实体的闭包类型中是否声明了额外的未命名的非静态数据成员是未指定的。


16、如果一个lambda-expression m2捕获了一个实体,并且那个实体被立即封闭的lambda-expression m1所捕获。那么m2的捕获作如下变换:

    ——如果m1通过拷贝来捕获该实体,那么m2捕获相应的m1闭包类型的非静态数据成员;

    ——如果m1通过引用捕获该实体,那么m2捕获了由m1所捕获的同一个实体。

[例:下列嵌套的lambda表达式和调用将输出123234

int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable {
    auto m2 = [a, b, &c]() mutable {
        std::cout << a << b << c;
        a = 4; b = 4; c = 4;
    };
    a = 3; b = 3; c = 3;
    m2();
};
a = 2; b = 2; c = 2;
m1();
std::cout << a << b << c;

——例结束]


17、由拷贝所捕获的使用一次定义规则的一个实体的每个id-expression被转换为对相应闭包类型的未命名数据成员的访问。[注:不使用一次定义规则的一个id-expression引用原始实体,而不会引用闭包类型的一个成员。此外,这样的一个id-expression不引发实体的隐式捕获。——注结束]如果this被捕获,那么每个对this使用一次定义规则被转换为对相应闭包类型的未命名数据成员的访问,投影(5.4)到this的类型。[注:该投影确保了被转换的表达式是一个纯右值。——注结束]

[例:

void f(const int*);
void g() {
    const int N = 10;
    [=] {
        int arr[N];    // OK:并没使用一次定义规则,引用自动变量
        f(&N);         // OK:引起N被捕获;&N指向相应闭包类型的成员
    };
}

—— 例结束]


18、decltype((x))的每次出现,这里x可能是被圆括号括起来的命名一个自动存储周期的一个实体的id-expression,被对待为就好似x被转换为对相应将可能被声明的闭包类型的一个数据成员的访问,如果x是对被指示实体使用一次定义规则的。

[例:

void f3() {
    float x, &r = x;
    [=] {                  // x和r不被捕获,(在一个decltype操作数中出现并不是使用一次定义规则的)
        decltype(x) y1;    // y1具有类型float
        decltype((x)) y2 = y1;  // y2具有类型float const &,因为这个lambda并非mutable,而且x是一个左值
        decltype(r) r1 = y1;    // r1具有类型float&(不考虑转换)
        decltype((r)) r2 = y2;  // r2具有类型float const &
    };
}
        

——例结束]


19、与一个lambda-expression相关联的闭包类型具有一个被删除的(8.4.3)构造器和一个被删除的拷贝赋值操作符。它具有一个隐式声明的拷贝构造器(12.8)并可能具有一个隐式声明的搬移构造器(12.8)。[注:拷贝/搬移构造器以与其它任何隐式声明的拷贝/搬移构造器会被隐式定义的相同的方式被隐式定义。——注结束]


20、与一个lambda-expression相关联的闭包类型具有一个隐式声明的构造器(12.4)。


21、当lambda-expression被计算时,被拷贝所捕获的实体被用于直接初始化每个相应的结果闭包对象的非静态数据成员。(对于数组成员,数组元素以递增下标次序被直接初始化。)这些初始化以非静态数据成员被声明的(未指定的)次序被执行。[注:这确保了销毁以构造相反的次序发生。——注结束]


22、[注:如果被引用所隐式或显式地捕获,那么在该实体的生命周期结束之后调用相应lambda-expression的函数调用操作符会导致未定义的行为。——注结束]


23、后面跟着一个省略号的一个capture是一个打包扩展(14.5.3)。[例:

template <class Args... args>
void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();
}

——例结束]

posted @ 2012-07-10 09:42  zenny_chen  Views(431)  Comments(0Edit  收藏  举报