web-dev-qa-db-ja.com

静的constexprメンバーの未定義の参照エラー

このコードを考えてみましょう:

#include <vector>

struct A {
  static constexpr int kDefaultValue = -1;
  std::vector<int> v;
  A(int n): v(n, A::kDefaultValue) {}
};

int main() {
  A(10);
  return 0;
}

リンクに失敗する(llvm clang、gcc 4.9、OS Xでは両方):

Undefined symbols for architecture x86_64:
  "A::kDefaultValue", referenced from:
      A::(int) in main.cpp.o
ld: symbol(s) not found for architecture x86_64

問題はそれの何が問題なのですか? static_cast- ing A::kDefaultValue to intで修正できます。または、kDefaultValueAから移動します。どちらのケースも醜いようです。これはリンクする別の方法ですか?

21
y0prst

この振る舞いは私を何度も悩ませています。トラブルの原因は、

_A(int n): v(n, A::kDefaultValue) {}
_

odr-uses _static constexpr_メンバー、vのコンストラクターは定数参照を取得するため第二引数。 Odrの使用には、どこかに定義が必要です。

_const int A::kDefaultValue;
_

一部のコンパイルユニット(コンパイルされ、main()にリンクされます)。この要件はC++ 17で削除され、対応する定義(上記)は廃止されました。

ただし、定義が常に可能であるとは限らず(たとえば、クラステンプレートのメンバーの場合)、定義とエラーの両方を回避する最も簡単な方法は次のとおりです。

_A(int n): v(n, int(A::kDefaultValue)) {}
_

これは、vのコンストラクターに渡される一時ファイルを作成します(ただし、後者は完全にインラインであるため、コンパイラーはそれを最適化する場合があります)。

19
Walter

C++ 17以降、動作が変更されました。 C++ 17より前は、 constexpr静的データメンバー でもクラス定義内で初期化する必要がありますが、名前空間スコープでの定義は引き続き必要です。 C++ 17以降、名前空間スコープ定義は再び必要ありません。

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

C++ 17をサポートするコンパイラを使用してコードをコンパイルすると、問題なく動作します。

gcc7を使用したライブデモ

11
songyuanyao