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++ 国際標準との互換性のために、constexpr 静的データ メンバーは、初期化子を使用せずにクラスの外部で重複して再宣言される場合があります。この使用法は非推奨です。[ 例:
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に拡張されました。
constexprC++11 では、スタティックメンバーがodr-usedでない場合、名前空間スコープ定義を提供する必要はありません。これは、ドラフト C++11 標準セクション9.4.2 [class.static.data]で確認できます。 (今後の重点鉱山):
[...]リテラル型の静的データ メンバーは、constexpr 指定子を使用してクラス定義で宣言できます。その場合、その宣言は、割り当て式であるすべての初期化子節が定数式であるブレースまたは等号初期化子を指定するものとします。[ 注: どちらの場合も、メンバーは定数式に現れることがあります。--end note ]
メンバーがプログラムで ODR 使用 (3.2)され、名前空間スコープ定義に初期化子が含まれていない場合、メンバーは名前空間スコープで定義されます。
それで、問題は、ここでbaz odr-usedです:
std::string str(baz);
答えはyesなので、名前空間スコープの定義も必要です。
では、変数がODR で使用されているかどうかをどのように判断するのでしょうか? 3.2 セクション[basic.def.odr]の元の C++11 の文言には、次のように書かれています。
式は、未評価のオペランド (第 5 節) またはその部分式でない限り、評価される可能性があります。名前が潜在的に評価される式として表示される変数は、それが定数式(5.19)に表示されるための要件を満たし、左辺値から右辺値への変換 (4.1) がすぐに適用される
オブジェクトでない限り、odr-usedです。
したがって、定数式がbaz生成されますが、配列であるため適用できないため、左辺値から右辺値への変換はすぐには適用されません。これはセクション[conv.lval]で説明されています。baz4.1
非関数、非配列型 Tの glvalue (3.10) は、 prvalue.53 に変換できます [...]
配列からポインタへの変換で適用されるもの。
[basic.def.odr]のこの文言は、一部のケースがこの文言でカバーされていないため、欠陥レポート 712により変更されましたが、これらの変更はこのケースの結果を変更しません。