簡體   English   中英

指向(數據)成員作為非類型模板參數的指針,例如具有自動存儲持續時間/無鏈接

[英]Pointer to (data) member as non-type template parameter for instance with automatic storage duration / without linkage

考慮以下片段:

#include <cstdint>
#include <iostream>

struct Foo {
    Foo() : foo_(0U), bar_(0U) {}

    void increaseFoo() { increaseCounter<&Foo::foo_>(); }
    void increaseBar() { increaseCounter<&Foo::bar_>(); }

    template <uint8_t Foo::*counter>
    void increaseCounter() { ++(this->*counter); }

    uint8_t foo_;
    uint8_t bar_;
};

void callMeWhenever() {
    Foo f;  // automatic storage duration, no linkage.
    f.increaseFoo();
    f.increaseFoo();
    f.increaseBar();

    std::cout << +f.foo_ << " " << +f.bar_;  // 2 1
}

int main() {
    callMeWhenever();
}

我的第一個猜測是這是callMeWhenever() ,因為callMeWhenever() f具有自動存儲持續時間,並且其地址在編譯時未知,而Foo的成員模板函數increaseCounter()使用指向Foo數據成員,以及給定類類型的內存表示是特定於編譯器的(例如填充)。 但是,從cppreference / Template parameters 和 template arguments ,afaics,這是格式良好的:

模板非類型參數

實例化具有非類型模板參數的模板時,以下限制適用:

[..]

[直到 C++17 ] 對於指向成員的指針,參數必須是指向成員的指針,表示為&Class::Member或計算結果為空指針或std::nullptr_t值的常量表達式。

[..]

[自 C++17 起] 唯一的例外是引用或指針類型的非類型模板參數 [自 C++20 起添加:以及類的非類型模板參數中的引用或指針類型的非靜態數據成員類型及其子對象 (C++20 起) ] 不能引用/成為

  • 一個子對象(包括非靜態類成員、基子對象或數組元素);
  • 一個臨時對象(包括在引用初始化期間創建的對象);
  • 字符串文字;
  • typeid 的結果;
  • 或預定義變量__func__

這是如何運作的? 編譯器(通過直接或間接,例如上述標准要求)是否需要自行解決這個問題,僅存儲成員之間的(編譯時)地址偏移量,而不是實際地址?

即/例如,是指向Foo::increaseCounter()的數據成員非類型模板參數counter的編譯時指針(對於指向數據成員實例的兩個特定指針中的每一個)只是任何給定實例化的編譯時地址偏移量Foo ,稍后將成為Foo每個實例的完全解析地址,即使尚未分配的地址,例如callMeWhenever()的塊范圍中的f

編譯器(通過直接或間接,例如上述標准要求)是否需要自行解決這個問題,僅存儲成員之間的(編譯時)地址偏移量,而不是實際地址?

差不多。 即使在編譯時上下文之外,它也是一個“偏移量”。 指向成員的指針與常規指針不同。 它們指定成員,而不是對象。 這也意味着關於有效指針目標的說法與指向成員的指針無關。

這就是為什么要從中產生實際的左值,必須用引用對象的東西來完成圖片,例如我們在this->*counter所做的。 如果您嘗試在需要常量表達式的地方使用this->*counter ,編譯器會抱怨,但它會是關於this ,而不是counter

它們的獨特性質使它們能夠無條件地成為編譯時常量。 沒有編譯器必須檢查為有效目標的對象。

正如 StoryTeller 已經提到的,成員指針不同於普通指針。 如果我們看一下由 clang 生成的(幾乎)未優化的程序集(完整代碼在這里),我們會看到模板的實例化:

void Foo::increaseCounter<&Foo::foo_>(): # @void Foo::increaseCounter<&Foo::foo_>()
        add     byte ptr [rdi], 1 
        ret

void Foo::increaseCounter<&Foo::bar_>(): # @void Foo::increaseCounter<&Foo::bar_>()
        add     byte ptr [rdi + 1], 1
        ret

因為這些是成員函數,所以rdi (這是第一個函數參數)保存了一個指向類實例的指針(在我們的例子中是this )。 因為Foo::foo_是第一個成員,它的地址與其類匹配,所以&f== &f.foo_ (其中fFoo一個實例)。 因此,對於Foo::foo_我們簡單地采取的地址this和增量到這個地址由1指向的字節。

第二種情況類似。 唯一的區別是Foo::bar_是類中的第二個數據成員,並且由於Foo::foo_僅占用 1 個字節的空間,因此Foo::bar_位於reinterpret_cast<char*>(this) + 1反映由rdi + 1

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM