簡體   English   中英

gcc和clang中constexpr靜態成員變量的鏈接器錯誤

[英]Linker error for constexpr static member variable in gcc and clang

我有一個片段:

enum class EC {a, b};

struct B {
    constexpr B(EC ec): ec_(ec) {}
    EC ec_;
};

struct A_base {
    constexpr A_base(B b): b_(b) { }
    B b_;
};

struct A: A_base {
    static constexpr B bbb = EC::a;
    constexpr A(B bbbb): A_base(bbbb) { }
};

int main()
{
    A a1(A::bbb);    // 1
    A a2{A::bbb};    // 2

    A a3 = A::bbb;   // 3
    A a4 = {A::bbb}; // 4
}

它通過支持c ++ 17的現代編譯器編譯好。 使用c ++ 11和c ++ 14標准支持鏈接器發生錯誤。 這個問題已在Linker錯誤(未定義的引用)中討論過`static constexpr const char *`和perfect-forwardingC ++ Linker Error With Class static constexpr和其他一些討論。 我理解為什么會出現這種錯誤。

但有些事情我不明白:

  1. 有了clang,我總是會遇到鏈接器錯誤。 但是當我設置-O3優化或聲明a1,a2,a3,a4變量為constexpr時,它內聯A :: bbb,並且沒有錯誤。
  2. 沒有優化的Gcc只需要為a3和a4引用A :: bbb。 通過優化和constexpr,它的行為與clang完全相同。

編譯器與關閉優化有何不同是正常的,為什么gcc將a1,a2初始化不等同於a3,a4?

在C ++之前17

A的所有初始化都調用A::A(B )構造函數,它需要復制一個B ,它使用編譯器生成的B::B(B const&) 將變量綁定到引用( A::bbb )是該變量的使用。 來自[basic.def.odr]的具體規則是:

變量x的名稱顯示為潛在評估的表達式ex,除非將lvalue-to-rvalue轉換(4.1)應用於x,否則會產生一個不調用任何非平凡函數的常量表達式(5.20)。 ,如果x是一個對象,則ex是表達式e的潛在結果集的一個元素,其中左值到右值的轉換(4.1)應用於e,或者e是丟棄值表達式(第5條) )。

對復制構造函數的第一次調用將涉及將A::bbb綁定到引用,該引用既不是左值到右值的轉換,也不是丟棄值表達式,因此它使用的是odr。

最重要的規則是:

每個程序應該只包含每個非內聯函數或變量的一個定義,該函數或變量在廢棄語句之外的程序中使用(6.4.1); 無需診斷。

A::bbb是odr,但缺乏定義,因此我們違反了該規則 - 通常稱為odr違規。 但由於在這種情況下編譯器不需要發出診斷(“無需診斷”或簡稱NDR),因此程序是未定義的行為。 對於程序員來說,這些類型的問題有時令人沮喪,但編譯器診斷它們是任意的 - 所以這是我們必須要忍受的。

很可能在更高的優化級別上,編譯器只是忽略副本,因此不需要為A::bbb調用B::B(B const&) ...至於為什么不同的初始化被區別對待? 可能是由於不同的優化過程。 最終,它不會改變這是一個odr違規的事實 - 無論代碼是否編譯和鏈接。


在C ++之后17

作為p0386的結果, static constexpr數據成員是隱式內聯的,這意味着現在A::bbb 確實有一個定義,現在沒有odr違規。 C ++ 17很酷。

暫無
暫無

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

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