名前空間
名前空間は大きなプロジェクトにおいて名前の衝突を防ぐための方法を提供します。
名前空間ブロックの内部で宣言されたシンボルは名前付きスコープ内に配置され、他のスコープにおける同一の名前のシンボルと間違えることを防ぎます。
同じ名前を持つ複数の名前空間ブロックを使用できます。 それらのブロック内のすべての宣言は名前付きスコープで宣言されます。
目次 |
[編集] 構文
namespace ns_name { declarations }
|
(1) | ||||||||
inline namespace ns_name { declarations }
|
(2) | (C++11以上) | |||||||
namespace { declarations }
|
(3) | ||||||||
ns_name:: name
|
(4) | ||||||||
using namespace ns_name;
|
(5) | ||||||||
using ns_name:: name;
|
(6) | ||||||||
namespace name = qualified-namespace ;
|
(7) | ||||||||
namespace ns_name:: inline (C++20以上)(オプション) name { declarations }
|
(8) | (C++17以上) | |||||||
namespace A::B::inline C { ... } は namespace A::B { inline namespace C { ... } } と同等です。 inline は最初以外のすべての名前空間名の前に現れることができます。 namespace A::inline B::C {} は namespace A { inline namespace B { namespace C {} } } と同等です。 |
(C++20以上) |
[編集] 説明
[編集] 名前空間
inline (オプション) namespace attr(オプション) identifier { namespace-body }
|
|||||||||
inline
|
- | 存在する場合は、これをインライン名前空間 (後述) とします。 原初名前空間の定義が inline を使用しなかった場合、拡張名前空間の定義に現れることはできません。
|
attr(C++17) | - | オプショナルな任意個の属性の並び。 |
identifier | - | それまで未使用であった識別子 (その場合、これは原初名前空間の定義です) または名前空間の名前 (その場合、これは拡張名前空間の定義です) または identifier で終わる :: 区切りの囲っている名前空間指定子の並び (その場合、これはネストした名前空間の定義です) (C++17以上) のいずれか。 |
namespace-body | - | 任意の種類の宣言 (クラス、関数、ネストした名前空間の定義を含みます) の並び (空でも構いません)。 |
名前空間の定義は名前空間スコープ (グローバルスコープを含みます) でのみ許されます。
既存の名前空間を開き直す (正式には拡張名前空間の定義) ためには、名前空間の定義で使用される identifier に対する名前探索は、囲っている名前空間または囲っている名前空間内のインライン名前空間のメンバとして宣言された名前空間名 (名前空間エイリアスではなく) に解決されなければなりません。
namespace-body は名前空間スコープを定義します。 これは名前探索に影響します。
namespace-body 内に現れる宣言によって導入されるすべての名前 (ネストした名前空間の定義を含みます) は、この名前空間の定義が原初名前空間の定義 (identifier を導入した) であるか拡張名前空間の定義 (すでに定義済みの名前空間を「開き直した」) かにかかわらず、名前空間 identifier のメンバとなります。
名前空間の本体内で宣言された名前空間メンバは明示的な修飾を用いてその外側で定義または再宣言しても構いません。
namespace Q { namespace V { // V は Q のメンバであり、 Q 内で完全に定義されます。 // namespace Q::V { // C++17 では上の2行の代わりにこのように書けます。 class C { void m(); }; // C は V のメンバであり、 V 内で完全に定義されます。 // C::m は宣言されるだけです。 void f(); // f は V のメンバですが、ここでは宣言されるだけです。 } void V::f() // V のメンバ f の V の外側での定義。 // f の囲っている名前空間はグローバル名前空間、 Q、 および Q::V のままです。 { extern void h(); // これは ::Q::V::h を宣言します。 } void V::C::m() // 名前空間 (およびクラス本体) の外側の V::C::m の定義。 // 囲っている名前空間はグローバル名前空間、 Q、 および Q::V です。 { } }
名前空間外の定義および再宣言は、宣言の地点より後、名前空間スコープでのみ、および原初名前空間を囲む名前空間 (グローバル名前空間を含みます) でのみ許されます。 また、修飾識別子の構文を使用しなければなりません。 (C++14以上)
namespace Q { namespace V { // V に対する原初名前空間の定義。 void f(); // Q::V::f の宣言。 } void V::f() {} // OK。 void V::g() {} // エラー、 g() はまだ V のメンバではありません。 namespace V { // V に対する拡張名前空間の定義。 void g(); // Q::V::g の宣言。 } } namespace R { // Q に対する囲っている名前空間ではありません。 void Q::V::g() {} // エラー、 R 内部で Q::V::g は定義できません。 } void Q::V::g() {} // OK、グローバル名前空間は Q を囲っています。
非ローカルクラス X 内のフレンド宣言によって導入された名前は X の最も内側の囲っている名前空間のメンバになりますが、通常の名前探索 (非修飾名も修飾名も) に対しては、マッチする宣言がクラス定義の前または後のいずれかで名前空間スコープに提供されない限り、可視になりません。 そのような名前は名前空間とクラスの両方を考慮する ADL を通して発見することができます。
名前が以前に宣言された名前と衝突するかどうかを決定するとき、そのようなフレンド宣言は最も内側の囲っている名前空間のみを考慮します。
void h(int); namespace A { class X { friend void f(X); // A::f はフレンドです。 class Y { friend void g(); // A::g はフレンドです。 friend void h(int); // A::h はフレンドです。 ::h と衝突しません。 }; }; // A::f、 A::g および A::h は、たとえ名前空間 A のメンバであっても、 // 名前空間スコープで可視ではありません。 X x; void g() { // A::g の定義。 f(x); // A::X::f は ADL を通して発見されます。 } void f(X) {} // A::f の定義。 void h(int) {} // A::h の定義。 // A::f、 A::g および A::h は今や名前空間スコープで可視であり、 // A::X、 A::X::Y のフレンドでもあります。 }
インライン名前空間インライン名前空間は原初名前空間の定義でオプショナルなキーワード インライン名前空間のメンバは、多くの状況 (下の一覧を参照) において、囲っている名前空間のメンバであるかのように扱われます。 この性質は推移的です。 名前空間 N がインライン名前空間 M を含み、名前空間 M がインライン名前空間 O を含む場合、 O のメンバは M や N のメンバであるかのように使用することができます。
{ // C++14 では、 std::literals およびそのメンバ名前空間はインラインです。 using namespace std::string_literals; // std::literals::string_literals の // operator""s を可視にします。 auto str = "abc"s; } { using namespace std::literals; // std::literals::string_literals::operator""s と // std::literals::chrono_literals::operator""s の // 両方を可視にします。 auto str = "abc"s; auto min = 60s; } { using std::operator""s; // std::literals::string_literals::operator""s と // std::literals::chrono_literals::operator""s の // 両方を可視にします。 auto str = "abc"s; auto min = 60s; } ノート: 特殊化に関するルールはライブラリのバージョニングを可能にします。 ライブラリテンプレートの異なる実装を異なるインライン名前空間に定義しながら、ユーザが親名前空間をプライマリテンプレートの明示的特殊化で拡張することを可能にします。 |
(C++11以上) |
[編集] 無名名前空間
無名名前空間の定義は以下の形式の名前空間の定義です。
inline (オプション) namespace attr(オプション) { namespace-body }
|
|||||||||
attr(C++17) | - | オプショナルな任意個の属性の並び。 |
この定義は一意な名前を持つ名前空間の定義として扱われ、その無名名前空間を指名する using 指令を現在のスコープに追加します。
namespace { int i; // ::(一意な名前)::i を定義します。 } void f() { i++; // ::(一意な名前)::i をインクリメントします。 } namespace A { namespace { int i; // A::(一意な名前)::i int j; // A::(一意な名前)::j } void g() { i++; } // A::(一意な名前)::i++ } using namespace A; // A からグローバル名前空間にすべての名前を導入します。 void h() { i++; // エラー、 ::(一意な名前)::i と ::A::(一意な名前)::i の両方がスコープ内にあります。 A::i++; // OK、 ::A::(一意な名前)::i をインクリメントします。 j++; // OK、 ::A::(一意な名前)::j をインクリメントします。 }
無名名前空間内の名前を外部リンケージを持つように宣言しても構いませんが、その名前空間の名前は一意であるため、他の翻訳単位からアクセスすることはできません。 |
(C++11未満) |
無名名前空間、および無名名前空間内で直接または間接的に宣言されたすべての名前空間は、内部リンケージを持ちます。 これは無名名前空間内で宣言されたあらゆる名前は内部リンケージを持つことを意味します。 |
(C++11以上) |
[編集] using 宣言
他の場所で定義された名前をこの using 宣言が現れた宣言領域に導入します。
using typename (オプション) nested-name-specifier unqualified-id ;
|
(C++17未満) | ||||||||
using declarator-list ;
|
(C++17以上) | ||||||||
nested-name-specifier | - | スコープ解決演算子で終わる、名前とスコープ解決演算子 :: の並び。 単一の :: はグローバル名前空間を参照します。
|
unqualified-id | - | 識別子式。 |
typename | - | using 宣言が基底クラスからクラステンプレートにメンバ型を導入するとき、依存名を解決するために必要に応じてキーワード typename が使用されることがあります。
|
declarator-list | - | typename (オプション) nested-name-specifier unqualified-id 形式の1個以上の宣言のコンマ区切りのリスト。 宣言子の後にパック展開を表す省略記号を続けても構いませんが、その形式は派生クラスの定義内でのみ意味を持ちます。
|
using 宣言は、名前空間のメンバを他の名前空間およびブロックスコープに、または基底クラスのメンバを派生クラスの定義に、導入するために使用することができます。
複数の using 宣言子を持つ using 宣言は、対応する1個の using 宣言子を持つ複数の using 宣言の並びと同等です。 |
(C++17以上) |
派生クラス定義内での使用については、 using 宣言を参照してください。
using 宣言によって名前空間スコープに導入された名前は、他のスコープからの修飾名の名前探索を含め、他のあらゆる名前と同様に使用できます。
void f(); namespace A { void g(); } namespace X { using ::f; // グローバルの f は ::X::f として��視になります。 using A::g; // A::g は ::X::g として可視になります。 using A::g, A::g; // (C++17) OK、名前空間スコープでの二重宣言は許されます。 } void h() { X::f(); // ::f を呼びます。 X::g(); // A::g を呼びます。 }
名前空間からメンバを取るために using 宣言が使用された後、その名前空間が拡張され同じ名前に対する追加の宣言が導入された場合、それらの追加の宣言は (using 指令とは対照的に) その using 宣言を通して可視になりません。 ひとつの例外は using 宣言がクラステンプレートを指すときです。 後に導入された部分特殊化は、その名前探索はプライマリテンプレートを通して処理されるため、実質的に可視になります。
namespace A { void f(int); } using A::f; // ::f は A::f(int) に対する同義語になります。 namespace A { // 名前空間の拡張。 void f(char); // ::f の意味は変わりません。 } void foo() { f('a'); // f(char) が存在していますが、 f(int) を呼びます。 } void bar() { using A::f; // この f は A::f(int) と A::f(char) の両方に対する同義語です。 f('a'); // f(char) を呼びます。 }
using 宣言は template-id、名前空間、またはスコープ付き列挙子を指すことはできません。 using 宣言内の宣言子はそれぞれ名前を1つだけ導入します。 たとえば列挙に対する using 宣言はその列挙子を導入しません。
同じ名前の普通の宣言に対するすべての制約 (隠蔽、およびオーバーロードのルール) が using 宣言に適用されます。
namespace A { int x; } namespace B { int i; struct g { }; struct x { }; void f(int); void f(double); void g(char); // OK、関数名 g は構造体 g を隠蔽します。 } void func() { int i; using B::i; // エラー、 i が2回宣言されています。 void f(char); using B::f; // OK、 f(char)、 f(int)、 f(double) はオーバーロードです。 f(3.5); // B::f(double) を呼びます。 using B::g; g('a'); // B::g(char) を呼びます。 struct g g1; // 構造体 B::g 型の変数 g1 を宣言します。 using B::x; using A::x; // OK、構造体 B::x を隠蔽します。 x = 99; // A::x に代入します。 struct x x1; // 構造体 B::x 型の変数 x1 を宣言します。 }
using 宣言によって関数が導入された場合、同じ名前および引数リストを持つ関数の宣言は ill-formed です (その宣言が同じ関数に対するものである場合を除きます)。 using 宣言によって関数テンプレートが導入された場合、同じ名前、引数型リスト、戻り値型およびテンプレート引数リストを持つ関数テンプレートの宣言は ill-formed です。 2つの using 宣言が同じ名前および引数リストを持つ関数を導入することはできますが、その関数への呼び出しが試みられた場合、プログラムは ill-formed です。
namespace B { void f(int); void f(double); } namespace C { void f(int); void f(double); void f(char); } void h() { using B::f; // B::f(int)、 B::f(double) を導入します。 using C::f; // C::f(int)、 C::f(double) および C::f(char) を導入します。 f('h'); // C::f(char) を呼びます。 f(1); // エラー、 B::f(int) と C::f(int) の間で曖昧です。 void f(int); // エラー、 f(int) は C::f(int) および B::f(int) と衝突します。 }
何らかの内側の名前空間でエンティティが宣言されたけれども定義されず、外側の名前空間で using 宣言を通して宣言され、そして外側の名前空間に同じ非修飾名を持つ定義が現れた場合、その定義は外側の名前空間のメンバであり、 using 宣言と衝突します。 namespace X { namespace M { void g(); // X::M::g() を宣言するけれども定義しません。 } using M::g; void g(); // エラー、 X::M::g() と衝突する X::g を宣言しようとしています。 } より一般的に言うと、何らかの名前空間スコープに現れ非修飾識別子を用いて名前を導入する宣言は、必ず、他のいかなる名前空間でもなく、その宣言が存在する名前空間にメンバを導入します。 例外はインライン名前空間で定義されたプライマリテンプレートの明示的実体化および明示的特殊化です。 それらは新しい名前を導入しないため、囲っている名前空間内の unqualified-id を使用できます。 |
(C++14以上) |
[編集] using 指令
using 指令は以下の構文を持つブロック宣言です。
attr(オプション) using namespace nested-name-specifier(オプション) namespace-name ;
|
(1) | ||||||||
attr(C++11) | - | この using 指令に適用する任意個の属性。 |
nested-name-specifier | - | スコープ解決演算子で終わる、名前とスコープ解決演算子 :: の並び。 単一の :: はグローバル名前空間を参照します。
|
namespace-name | - | 名前空間の名前。 この名前を探すとき、名前探索は名前空間宣言のみを考慮します。 |
using 指令は名前空間スコープおよびブロックスコープでのみ使用できます。 using 指令の後かつそれが現れたスコープの終わりまでのあらゆる名前の非修飾名の名前探索の観点において、 namespace-name のすべての名前は、その using 指令と namespace-name の両方を含む最も近い囲っている名前空間で宣言されたかのように、可視です。
using 指令は (using 宣言と異なり) それが現れる宣言領域にいかなる名前も導入せず、同じ名前が宣言されることを妨げません。
using 指令は非修飾名の名前探索の目的に対して推移的です。 あるスコープが namespace-name を指名する using 指令を含み、 namespace-name が何らかの namespace-name-2 に対する using 指令を含む場合、その効果は、第2の名前空間内の using 宣言が第1の名前空間内に現れたかのようになります。 これらの推移的な名前空間が現れる順序は名前探索に影響しません。
namespace A { int i; } namespace B { int i; int j; namespace C { namespace D { using namespace A; // A 内のすべての名前がグローバル名前空間に注入されます。 int j; int k; int a = i; // A::i は B::i によって隠蔽されるため、 i は B::i です。 } using namespace D; // D 内の名前が C に注入され、 // A 内の名前がグローバル名前空間に注入されます。 int k = 89; // using 指令によって導入されたものと同じ名前を宣言できます。 int l = k; // C::k または D::k の間で曖昧です。 int m = i; // OK、 B::i は A::i を隠蔽します。 int n = j; // OK、 D::j は B::j を隠蔽します。 } }
using 指令が何らかの名前空間を指名するために使用された後、名前空間が拡張され、追加のメンバや using 指令が追加された場合、それらの追加のメンバおよび追加の名前空間は (using 宣言と対照的に) using 指令を通して可視になります。
namespace D { int d1; void f(char); } using namespace D; // D::d1、 D::f、 D::d2、 D::f、 E::e、 E::f を // グローバル名前空間に導入します。 int d1; // OK、宣言するときは D::d1 と衝突しません。 namespace E { int e; void f(int); } namespace D { // 名前空間の拡張。 int d2; using namespace E; // 推移的な using 指令。 void f(int); } void f() { d1++; // エラー、 ::d1 と D::d1 の間で曖昧です。 ::d1++; // OK。 D::d1++; // OK。 d2++; // OK、 d2 は D::d2 です。 e++; // OK、推移的な using 指令により e は E::e です。 f(1); // エラー、 D::f(int) と E::f(int) の間で曖昧です。 f('a'); // OK、唯一の f(char) は D::f(char) です。 }
[編集] ノート
何らかの名前空間での using 指令 using namespace std;
は名前空間 std
のすべての名前をグローバル名前空間に導入します (グローバル名前空間が std
と何らかのユーザ宣言された名前空間の両方を含む最も近い名前空間であるため)。 これは意図しない名前の衝突につながる可能性があります。 ヘッダファイルのファイルスコープでは、これ (および他の using 指令) は一般的には悪い習慣であると考えられています。
[編集] 例
この例はすでに std
名前空間で名付けられているクラスを作成するために名前空間を使用する方法を示します。
#include <vector> namespace vec { template< typename T > class vector { // ... }; } // vec の終わり。 int main() { std::vector<int> v1; // 標準の vector。 vec::vector<int> v2; // ユーザ定義の vector。 v1 = v2; // エラー、 v1 と v2 は異なる型です。 { using namespace std; vector<int> v3; // std::vector と同じ。 v1 = v3; // OK。 } { using vec::vector; vector<int> v4; // vec::vector と同じ。 v2 = v4; // OK。 } return 0; }
[編集] 欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
DR | 適用先 | 発行時の動作 | 正しい動作 |
---|---|---|---|
CWG 1838 | C++14 | unqualified definition in an outer namespace could define an entity declared,
but not defined in another namespace and pulled in by a using |
unqualified defns always refers to its namespace |
[編集] 関連項目
名前空間エイリアス | 既存の名前空間のエイリアスを作成します |