次の簡単なプログラムを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++
バグですか、それともこの厄介な問題を回避するためにトリックを使用する必要がありますか?
これはバグだとは思いません。 constexpr
をconst
に変更しても、まったく同じエラーで失敗します。
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
}
この理由は、constexpr
canはコンパイル時に評価されますが、-必須はそのように評価されず、実行時に同様に発生する可能性があるためです。 "コンパイラーは常にシンボルを式として扱うように指示します"を指示しません。コンパイラーがそのように感じた場合、そうすることは賢明で許容できることを示唆します。
エラーの理由はすでに説明されているので、回避策を追加します。
return std::min(int(S::X), 0);
これは一時的なものを作成するので、std::min
はそれへの参照を取ることができます。
これはC++ 17で修正されました。
https://en.cppreference.com/w/cpp/language/static :
静的データメンバーがconstexprとして宣言されている場合、それは暗黙的にインラインであり、名前空間スコープで再宣言する必要はありません。イニシャライザ(以前は上記のように必須でした)なしのこの再宣言は引き続き許可されますが、非推奨です。
また、構造体(またはクラス)の外にある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);
};
C++標準( 最新の草案 )では、次のように述べています。
名前空間スコープ(3.3.6)を持つ名前は、明示的に
const
またはconstexpr
と宣言され、extern
も以前に外部リンケージ[...]を持つように宣言されていません。
「リンケージ」は次のように定義されています。
別のスコープの宣言によって導入された名前と同じオブジェクト、参照、関数、型、テンプレート、名前空間、または値を表す可能性がある場合、名前はリンケージがあるといいます。
—名前に外部リンケージがある場合、それが示すエンティティは、他の翻訳単位のスコープまたは同じ翻訳の他のスコープからの名前で参照できます単位。
—名前に内部リンケージが含まれている場合、その名前が示すエンティティは、同じ翻訳単位内の他のスコープの名前で参照できます。
—名前にリンケージがない場合、その名前が示すエンティティは、他のスコープの名前から参照できません。
したがって、namespace S
の場合、外部リンケージがあり、struct S
の場合、内部リンケージ。
外部リンケージのあるシンボルは、いくつかの変換単位でシンボルを明示的に定義する必要があります。
constexpr
の理解が間違っています。 constexpr
と宣言された左辺値は依然として左辺値であり、constexpr
と宣言された関数は依然として関数です。また、関数に参照パラメーターがあり、左辺値が渡される場合、言語は、参照がその左辺値を参照することを必要とし、それ以外は何も要求しません。 (int
型の変数に適用すると、constexpr
と単純なconst
の違いはほとんどありません。)