天天看點

C++進階學習(三)constexpr關鍵字、值類别與decltype關鍵字、lambda表達式

五、constexpr說明符

  • constexpr說明符聲明該變量或函數在編譯期進行求值,進而适用于需要編譯器常量表達式的地方
  • 在變量聲明constexpr時,對象或非靜态成員函數蘊含const,函數或靜态成員變量蘊含inline
  • constexpr變量必須立刻被初始化
constexpr int a = 5;
// a = 6; /*error*/
           
  • 如果一個函數或模闆的一個聲明具有constexpr,那麼該函數或模闆的所有聲明都必須具有constexpr
  • const承擔“隻讀”的修飾,而constexpr承擔“常量”的修飾,并且在編譯期就可以求出該變量或函數的值
int num = 5;
// constexpr auto n1 = num; /*error*/
// constexpr array<int, num> arr1(); /*error*/
constexpr auto n2 = 4;
constexpr array<int, n2> arr2();
           

在上面的例子中,n1與arr1的定義都需要num的參與,而num在編譯期無法獲得它的值,故無法正确定義n1和arr1。而n2是constexpr的,故arr2在編譯期可以得到n2的值。

六、值類别與decltype

  • 每個表達式都屬于三種基本值類别中的一種:純右值、亡值、左值
  • 我們可以通過decltype判斷一個表達式的值類别,若産生T&&,則為亡值;若産生T&,則為左值;若産生T,則為純右值
  • 除了字元串字面量,其他所有字面量都為純右值字面量
  • const T&方式的左值引用可以接收右值(純右值和亡值)
int operator""_i(size_t num)
{
    return num;
}

int num(int n)
{
    return n;
}

void func(const int &num)
{
    cout << num << endl;
}

int main()
{
    cout << "---begin---" << endl;
    int val{0};
    // 以下表達式均能傳參成功,即均為右值
    func(val);
    func(num(5));
    func(move(val));
    func(12_i);
    cout << "---end---" << endl;
    return 0;
}
           
  • 使用decltype時,帶有括号的對象通常被認為是左值表達式
int main()
{
    cout << "---begin---" << endl;
    int val{0};
    using T1 = decltype((val));
    using T2 = decltype(val);
    T2 b = 5;
    T1 a = b;
    cout << &a << endl;
    cout << &b << endl;
    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
0x8f29bff8ac
0x8f29bff8ac
---end---
           

可以看出,當我們把b指派給a後,兩者的位址相同,易見T1是T2的引用類型。

七、lambda表達式

  • lambda是一個無名非聯合非聚合類類型,被稱為閉包類型,類的操作同樣适用于它(注:下列代碼隻适用于C++20之後的版本)
auto lambda = [](int n)
{ cout << n << endl; };
// 繼承适用
struct Test : decltype(lambda){};

int main()
{
    cout << "---begin---" << endl;
    Test test;
    test(1);

    // 指針适用
    auto ptr = make_shared<decltype(lambda)>(lambda);
    (*ptr)(2);
    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
1
2
---end---
           
  • lambda對象常用auto進行自動類型推導
  • lambda捕獲符隻有=和&,其中,=是以複制的形式進行捕獲,&是通過引用的形式進行捕獲
  • 捕獲的組合應保證每個變量的捕獲不重複,且名字不能與傳入形參相同
int main()
{
    cout << "---begin---" << endl;
    int a = 5, b = 5;

    // 值傳遞捕獲指定對象
    auto ptr1 = [a]
    {
        cout << a << endl;
        // b++; /*error*/
    };
    ptr1();

    // 引用傳遞捕獲指定對象
    auto ptr2 = [&a]
    {
        cout << ++a << endl;
    };
    ptr2();
    cout << a << endl;

    // 值傳遞捕獲所有變量
    auto ptr3 = [=]
    {
        cout << a << ' ' << b << endl;
        // a++, b++; /*error*/
    };
    ptr3();

    // 引用傳遞捕獲所有變量
    auto ptr4 = [&]
    {
        cout << ++a << ' ' << ++b << endl;
    };
    ptr4();

    // 值傳遞捕獲所有變量,引用傳遞捕獲部分變量
    auto ptr5 = [=, &a]
    {
        cout << a << ' ' << b << endl;
        a++;
        // b++; /*error*/
    };
    ptr5();

    // auto ptr6 = [&, &a] {}; /*error:對a重複進行引用捕獲*/

    // auto ptr7 = [&a](int a){}; /*error:捕獲的變量名與傳入形參名稱相同*/

    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
5
6
6
6 5
7 6
7 6
---end---
           
  • lambda捕獲對象預設為const,若使用mutable進行修飾則可以去除const
int main()
{
    cout << "---begin---" << endl;
    // auto p = [num = 0]
    // {
    //     num++; /*error*/
    //     cout << num << endl;
    // };
    auto p = [num = 0] mutable
    {
        num++;
        cout << num << endl;
    };
    p();
    cout << "---end---" << endl;
    return 0;
}
           
  • 對所有局部變量進行捕獲時,隻會捕獲lambda體中被調用的對象
int main()
{
    cout << "---begin---" << endl;
    int a = 5, b = 5;

    auto ptr1 = [=] {};
    auto ptr2 = [=]
    {
        cout << a << endl;
    };
    cout << "ptr1 memory:" << sizeof ptr1 << "\nptr2 memory:" << sizeof ptr2 << endl;

    cout << "---end---" << endl;
    return 0;
}
           

在上面的執行個體中,ptr1雖然為全域捕獲,但lambda體中沒有使用外部變量,是以實際上為空類,故隻有1位元組來進行定址;而ptr2中由于隻使用了對象a,故記憶體大小為4。

  • 如果變量是非局部變量,或具有靜态或線程局部存儲期的時候,或該變量是以常量表達式初始化的引用,lambda表達式在使用該變量前不需要對其進行捕獲
int main()
{
    cout << "---begin---" << endl;
    static int a = 5;
    int b = 5;
    auto ptr = [=]()
    {
        a++;
        // b++; /*error*/
    };
    ptr();
    cout << a << endl;
    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
6
---end---
           
  • 如果變量滿足下列條件時,lambda在讀取它的值前不需要進行捕獲:該變量具有const而非volatile的整型或枚舉類型并已經用常量表達式初始化,或該變量是constexpr的且沒有mutable成員
int main()
{
    cout << "---begin---" << endl;
    const int a = 5;

    auto ptr = []
    {
        cout << a << endl;
    };
    ptr();
    cout << "size:" << sizeof ptr << endl;

    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
5
size:1
---end---
           

可以看到,lambda體可以正常讀取a的值,但本身仍為一個空類,可見lambda在讀取a前并沒有捕獲a。

但是,我們并不能直接使用該常量:

const int a = 5;

auto ptr = []
{
    // cout << &a << endl; /*error*/
};
           
  • 若外部對象在typeid中被使用,lambda體會捕獲該const對象
int main()
{
    cout << "---begin---" << endl;
    const int a = 5;

    auto ptr = [=](auto n)
    {
        typeid(a + n);
    };
    cout << "size:" << sizeof ptr << endl;

    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
size:4
---end---
           
  • 泛型lambda:在lambda的形參中使用auto傳參,都會虛設一個與auto參數順序相對應的模闆形參,效果與顯式的模闆參數類似
int main()
{
    cout << "---begin---" << endl;
    auto p = [](auto a, auto b)
    {
        return a + b;
    };
    cout << p(1, 2) << endl;
    cout << p(string("1"), string("2")) << endl;

    auto p2 = []<typename T>(T a, T b)
    {
        return a + b;
    };
    cout << p2(1, 2) << endl;
    cout << p2(string("1"), string("2")) << endl;
    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
3
12
3
12
---end---
           
  • 每一個lambda都對應了一個不同的類,如果要保持lambda的泛型性進行存儲,可以使用C++17中的any,并通過any_cast重新擷取對象
int main()
{
    cout << "---begin---" << endl;
    auto ptr1 = [](auto x)
    { cout << x << endl; };
    auto ptr2 = [](auto x)
    { cout << x + 1 << endl; };
    vector<any> v = {ptr1, ptr2};
    any_cast<decltype(ptr1)>(v[0])(1);
    any_cast<decltype(ptr2)>(v[1])(2);
    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
1
3
---end---
           
  • lambda可以通過轉換函數變為函數指針,而泛型lambda轉化為函數指針時将無法保持泛型性,但指定類型後仍可以正常轉化
int main()
{
    cout << "---begin---" << endl;
    auto ptr1 = [](auto x)
    { cout << x << endl; };
    // void (*p0)(auto x) = ptr1; /*error*/
    void (*p)(int x) = ptr1;
    (*p)(1);
    cout << "---end---" << endl;
    return 0;
}
           

輸出結果:

---begin---
1
---end---
           
  • 可以顯示指定lambda為constexpr,但如果沒有指定constexpr,而能滿足constexpr的所有要求,那麼它也将是constexpr的

繼續閱讀