web-dev-qa-db-ja.com

クラスstatic constexprでのC ++リンカーエラー

次の簡単なプログラムをg++-4.6.1 --std=c++0xでコンパイルしています。

#include <algorithm>

struct S
{
    static constexpr int X = 10;
};

int main()
{
    return std::min(S::X, 0);
};

次のリンカーエラーが発生します。

/tmp/ccBj7UBt.o: In function `main':
scratch.cpp:(.text+0x17): undefined reference to `S::X'
collect2: ld returned 1 exit status

インラインで定義された静的メンバーにはシンボルが定義されていないことに気づきましたが、constexprを使用すると、常にシンボルを式として扱うようコンパイラーに指示するという(おそらく欠陥がある)印象を受けました。そのため、コンパイラは、シンボルS::Xへの参照を渡すことが不正であることを認識します(同じ理由で、リテラル10への参照を取得できません)。

ただし、Sが名前空間として宣言されている場合、つまり「構造体S」ではなく「名前空間S」の場合、すべて正常にリンクされます。

これはg++バグですか、それともこの厄介な問題を回避するためにトリックを使用する必要がありますか?

48
Travis Gockel

これはバグだとは思いません。 constexprconstに変更しても、まったく同じエラーで失敗します。

S::Xを宣言しましたが、どこにも定義していないため、ストレージがありません。アドレスを知る必要がある何かを行う場合は、どこかで定義する必要があります。

例:

int main() {
      int i = S::X; // fine
      foo<S::X>(); // fine
      const int *p = &S::X; // needs definition
      return std::min(S::X, 0); // needs it also
}

この理由は、constexprcanはコンパイル時に評価されますが、-必須はそのように評価されず、実行時に同様に発生する可能性があるためです。 "コンパイラーは常にシンボルを式として扱うように指示します"を指示しません。コンパイラーがそのように感じた場合、そうすることは賢明で許容できることを示唆します。

32
Flexo

エラーの理由はすでに説明されているので、回避策を追加します。

return std::min(int(S::X), 0);

これは一時的なものを作成するので、std::minはそれへの参照を取ることができます。

12
VladV

これはC++ 17で修正されました。

https://en.cppreference.com/w/cpp/language/static

静的データメンバーがconstexprとして宣言されている場合、それは暗黙的にインラインであり、名前空間スコープで再宣言する必要はありません。イニシャライザ(以前は上記のように必須でした)なしのこの再宣言は引き続き許可されますが、非推奨です。

8
Trass3r

また、構造体(またはクラス)の外にあるconstexprメンバーの定義を提供する必要がありますが、今回はその値はありません。ここを参照してください: https://en.cppreference.com/w/cpp/language/static

#include <algorithm>

struct S
{
    static constexpr int X = 10;
};

constexpr int S::X;

int main()
{
    return std::min(S::X, 0);
};
6
jciloa

C++標準( 最新の草案 )では、次のように述べています。

名前空間スコープ(3.3.6)を持つ名前は、明示的にconstまたはconstexprと宣言され、externも以前に外部リンケージ[...]を持つように宣言されていません。

「リンケージ」は次のように定義されています。

別のスコープの宣言によって導入された名前と同じオブジェクト、参照、関数、型、テンプレート、名前空間、または値を表す可能性がある場合、名前はリンケージがあるといいます。

—名前に外部リンケージがある場合、それが示すエンティティは、他の翻訳単位のスコープまたは同じ翻訳の他のスコープからの名前で参照できます単位。

—名前に内部リンケージが含まれている場合、その名前が示すエンティティは、同じ翻訳単位内の他のスコープの名前で参照できます。

—名前にリンケージがない場合、その名前が示すエンティティは、他のスコープの名前から参照できません。

したがって、namespace Sの場合、外部リンケージがあり、struct Sの場合、内部リンケージ

外部リンケージのあるシンボルは、いくつかの変換単位でシンボルを明示的に定義する必要があります。

5
Albert

constexprの理解が間違っています。 constexprと宣言された左辺値は依然として左辺値であり、constexprと宣言された関数は依然として関数です。また、関数に参照パラメーターがあり、左辺値が渡される場合、言語は、参照がその左辺値を参照することを必要とし、それ以外は何も要求しません。 (int型の変数に適用すると、constexprと単純なconstの違いはほとんどありません。)

1
James Kanze