web-dev-qa-db-ja.com

静的constexpr char []への未定義の参照

クラスにstatic constchar配列が必要です。 GCCは文句を言ってconstexprを使うべきだと言ったが、今では未定義の参照だと言っている。配列を非メンバーにすると、コンパイルされます。何が起こっている?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
164
Pubby

Cppファイルに追加します。

constexpr char foo::baz[];

理由:静的メンバーのdefinitionと宣言を提供する必要があります。宣言と初期化子はクラス定義の内部に入りますが、メンバー定義は分離する必要があります。

159
Kerrek SB

C++ 17はインライン変数を導入します

C++ 17は、ord使用された場合に行外定義を必要とするconstexpr静的メンバー変数のこの問題を修正します。 C++ 17以前の詳細については、以下の元の回答を参照してください。

提案 P0386インライン変数 は、インライン指定子を変数に適用する機能を導入します。特にこの場合、constexprは静的メンバー変数のインラインを意味します。提案は言う:

インライン指定子は、関数だけでなく変数にも適用できます。インラインで宣言された変数は、インラインで宣言された関数と同じセマンティクスを持ちます。同じように、複数の翻訳単位で定義できます。正確に1つの変数。

変更された[basic.def] p2:

宣言は、次の場合を除き定義です。
...

  • クラス定義の外部で静的データメンバーを宣言し、constexpr指定子を使用してクラス内で変数が定義されました(この使用法は非推奨です。[depr.static_constexpr]を参照)。

...

[depr.static_constexpr] を追加します:

以前のC++国際標準との互換性のために、constexpr静的データメンバは、イニシャライザなしでクラスの外部で冗長に再宣言される場合があります。この使用法は非推奨です。 [例:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

—例を終了]

元の回答

C++ 03では、C++でconst積分またはconst列挙型のクラス初期化子のみを提供できました。 11constexprを使用して、これはliteral typesに拡張されました。

C++ 11では、odr-usedでない場合、静的constexprメンバーの名前空間スコープ定義を提供する必要はありません。これはドラフトC++から確認できます。 11標準セクション9.4.2[class.static.data]これは(今後のエンファシス鉱山):

[...]リテラル型の静的データメンバーは、constexpr指定子を使用してクラス定義で宣言できます。その場合、その宣言は、brace-or-equal-initializerを指定するものとします。このイニシャライザー句は、assignment-expressionであるすべてのinitializer-clauseが定数式です。 [注:どちらの場合も、メンバーは定数式に表示される場合があります。 --end note] プログラムでodr-used(3.2)であり、名前空間スコープ定義に初期化子が含まれていない場合、メンバーは名前空間スコープで定義されます。

質問はbazodr-usedになります:

std::string str(baz); 

答えはyesであるため、名前空間スコープ定義も必要です。

では、変数がodr-usedであるかどうかをどのように判断するのでしょうか?セクション3.2[basic.def.odr]の元のC++ 11の文言は次のとおりです。

式は、未評価のオペランド(5項)またはその部分式でない限り、潜在的に評価されます。評価される可能性のある式として名前が表示される変数次の場合を除き、odrが使用されますそれは、定数式に表示するための要件を満たしている(5.19)および- 左辺値から右辺値への変換(4.1)がすぐに適用されます

したがって、baz定数式を生成しますが、lvalue-to-rvalue変換はすぐに適用されません。 bazが配列であるため、適用できません。これについては、セクション4.1[conv.lval]で説明されています。

非関数のglvalue(3.10)、非配列型Tはprvalue.53 [...]に変換できます。

配列からポインターへの変換に適用されるもの

[basic.def.odr]のこの文言は、 障害レポート712 により変更されました。これは、この文言ではカバーされないケースもありますが、これらの変更はカバーしないためです。この場合の結果を変更します。

62
Shafik Yaghmour

これは本当にC++ 11の欠陥です-他の人が説明したように、C++ 11では静的constexprメンバー変数は、他の種類のconstexprグローバル変数とは異なり、外部リンケージを持っているため、どこかで明示的に定義する必要があります。

また、すべての用途でインライン化される可能性があるため、実際には最適化でコンパイルするときに定義なしで静的constexprメンバー変数を実際に回避できることも注目に値しますが、最適化せずにコンパイルすると、プログラムがリンクに失敗することがよくあります。これにより、これは非常に一般的な隠されたtrapになります。プログラムは最適化で正常にコンパイルされますが、最適化をオフにするとすぐに(おそらくデバッグのために)リンクに失敗します。

しかし、朗報です-この欠陥はC++ 17で修正されました!ただし、このアプローチは少し複雑です。C++ 17では、静的constexprメンバー変数 暗黙的にインライン です。 変数にインラインを適用 を持つことは、C++ 17の新しい概念ですが、それは事実上、どこにも明示的な定義を必要としないことを意味します。

34
SethML

char[]を次のように変更することは、よりエレガントなソリューションではありません。

static constexpr char * baz = "quz";

これにより、1行のコードで定義/宣言/初期化子を使用できます。

5
deddebme

静的メンバーの外部リンクの私の回避策は、constexpr参照メンバーゲッター(@deddebmeからの回答に対するコメントとして発生した問題@gnzlbgに遭遇しない)を使用することです。
このイディオムは、プロジェクトに複数の.cppファイルがあることを嫌って、#includesとmain()関数のみで構成される1つに制限しようとするため、私にとって重要です。 。

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
2
Josh Greifer

私の環境では、gccバージョンは5.4.0です。 「-O2」を追加すると、このコンパイルエラーを修正できます。最適化を求めるときにgccがこのケースを処理できるようです。

0
Haishan Zhou