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


186

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

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

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

1
ちょっとした勘ですが、たとえばbazがintの場合は機能しますか?次にアクセスできますか?バグかもしれません。
FailedDev

1
@Pubby:質問:どの翻訳単位で定義されますか?回答:ヘッダーを含むすべてのもの。問題:1つの定義ルールに違反します。例外:コンパイル時の定数積分はヘッダーで「初期化」できます。
Mooing Duck

int@MooingDuck として正常にコンパイルされます。非メンバーとして正常に動作します。それもルールに違反しませんか?
Pubby

@ Pubby8:intチート。非メンバーとして、C ++ 11のルールが変更されない限り、それは許可されるべきではありません(可能)
Mooing Duck

見解と賛成意見を考慮すると、この質問にはより詳細な回答が必要でした。これを以下に追加しました。
Shafik Yaghmour 2015年

回答:


188

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

constexpr char foo::baz[];

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


70
それは奇妙に見えます...それは以前に持っていなかったいくつかの情報をコンパイラに提供していないようです...
vines

32
.cppファイルにクラス宣言があると、さらに奇妙に見えます。クラス宣言でフィールドを初期化しますが、クラスの下にconstexpr char foo :: baz []を書き込んでフィールドを「宣言」する必要があります。constexprを使用するプログラマーは、奇妙なヒントの1つであるプログラムを再度宣言することで、プログラムをコンパイルできるようです。
Lukasz Czerwinski 14年

5
@LukaszCzerwinski:あなたが探している言葉は「定義する」です。
Kerrek SB、2014

5
右、新しい情報:使用して宣言decltype(foo::baz) constexpr foo::baz;
ない-ユーザー

6
fooがテンプレート化されている場合、式はどのようになりますか?ありがとう。
Hei

80

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

C ++ 17 constexpr staticは、odrで使用された場合に行外定義を必要とするメンバー変数のこの問題を修正します。C ++ 17より前の詳細については、この回答の後半を参照してください。

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

インライン指定子は、関数だけでなく変数にも適用できます。インラインで宣言された変数は、インラインで宣言された関数と同じセマンティクスを持っています。複数の翻訳単位で同じように定義でき、odrを使用するすべての翻訳単位で定義する必要があり、プログラムの動作は次のようになります。変数は1つだけです。

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


...でない限り、宣言は定義です。

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

...

そして[depr.static_constexpr]を追加します

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

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

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

 —例を終了]


C ++ 14およびそれ以前

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

C ++ 11では、我々は静的のための名前空間スコープを定義する必要はありません。constexprそうでない場合はメンバーのODR-使用し、我々はドラフトC ++ 11標準のセクションからこれを見ることができます9.4.2 [class.static.data]と言います(今後の重点鉱山):

[...]リテラル型の静的データメンバーは、constexpr指定子を使用してクラス定義で宣言できます。もしそうなら、その宣言は中括弧イコールイニシャライザを指定しなければなりません。ここで、代入式であるすべての初期化句は定数式です。[注:これらのどちらの場合でも、メンバーは定数式に現れることがあります。—end note] プログラムでメンバーがodr-used(3.2)であり、ネームスペーススコープ定義に初期化子が含まれていない場合、メンバーはネームスペーススコープで定義されます。

それで問題は、ここでbaz odr-usedです:

std::string str(baz); 

答えはyesなので、名前空間スコープの定義も必要です。

それでは、変数がodrで使用されているかどうかをどのように判断しますか?セクション3.2 [basic.def.odr]の元のC ++ 11表現は次のように述べています。

式は、未評価のオペランド(第5項)またはその部分式でない限り、潜在的に評価されます。名前が潜在的に評価された式として表示される変数は、定数式(5.19)に表示されるための要件を満たし、左辺値から右辺値への変換(4.1)がすぐに適用される オブジェクトでない限りodr使用されます。

したがってbaz定数式が生成されますが、左辺値から右辺値への変換はbaz、配列であるため適用できないため、すぐには適用されません。これについては、次のセクション4.1 [conv.lval]説明されています。

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

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

[basic.def.odr]のこの表現は、一部のケースがこの表現でカバーされなかったため、不具合レポート712により変更されましたが、これらの変更はこのケースの結果を変更しません。


constexprそれとまったく関係がないことは明らかですか?(bazとにかく定数式です)
MM

@MattMcNabb well constexprは、メンバーがaではない場合に必要ですintegral or enumeration typeが、そうでない場合、はい、重要なのは定数式であることです。
Shafik Yaghmour 2015年

最初の段落では、「ord-used」は「odr-used」と読むべきだと思いますが、C ++では確信が持てません
Egor Pasko

37

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

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

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


4
C ++ 17情報のアップ。この情報を承認済みの回答に追加できます。
SR

5

よりエレガントな解決策はに変更することではありませんchar[]

static constexpr char * baz = "quz";

このようにして、1行のコードで定義/宣言/初期化子を含めることができます。


9
char[]使用sizeofすると、コンパイル時に文字列の長さを取得できますが、取得char *できません(ポインタ型の幅(この場合は1)を返します)。
gnzlbg 2016年

2
これは、ISO C ++ 11に厳密にしたい場合にも警告を生成します。
Shital Shah 2017

sizeof問題を示さず、「ヘッダーのみ」のソリューションで使用できる私の回答をご覧ください
Josh Greifer

4

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

// 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]'

-1

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.