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 宣言は、名前空間のメンバを他の名前空間およびブロックスコープに導入するために、または基底クラスのメンバを派生クラスの定義に導入するために、使用することができます。
1つ以上の using 宣言子を持つ using 宣言は、1つの using 宣言子を持つ一連の using 宣言と同等です。 |
(C++17以上) |
[編集] 名前空間およびブロックスコープにおいて
using 宣言は別の名前空間のメンバを現在の名前空間またはブロックスコープに導入します。
#include <iostream> #include <string> using std::string; int main() { string str = "Example"; using std::cout; cout << str; }
詳細は名前空間を参照してください。
[編集] クラス定義において
using 宣言は基底クラスのメンバを派生クラスの定義の導入します (基底のプロテクテッドメンバを派生のパブリックメンバとして露出させるためなど)。 この場合、 nested-name-specifier は定義中のクラスの基底クラスの名前でなければなりません。 その名前が基底クラスのオーバーロードされたメンバ関数の名前である場合、その名前を持つ基底クラスのメンバ関数すべてが導入されます。 派生クラスが同じ名前、同じ引数リストおよび同じ修飾のメンバをすでに持っている場合は、派生クラスのメンバが基底クラスから導入されたメンバを隠蔽またはオーバーライドします (衝突しません)。
#include <iostream> struct B { virtual void f(int) { std::cout << "B::f\n"; } void g(char) { std::cout << "B::g\n"; } void h(int) { std::cout << "B::h\n"; } protected: int m; // B::m は protected です。 typedef int value_type; }; struct D : B { using B::m; // D::m は public です。 using B::value_type; // D::value_type は public です。 using B::f; void f(int) { std::cout << "D::f\n"; } // D::f(int) は B::f(int) をオーバーライドします。 using B::g; void g(int) { std::cout << "D::g\n"; } // g(int) と g(char) の両方が // D のメンバとして可視になります。 using B::h; void h(int) { std::cout << "D::h\n"; } // D::h(int) が B::h(int) を隠蔽します。 }; int main() { D d; B& b = d; // b.m = 2; // エラー。 B::m は protected です。 d.m = 1; // protected な B::m は public な D::m としてアクセス可能です。 b.f(1); // D::f(int) を呼びます。 d.f(1); // D::f(int) を呼びます。 d.g(1); // D::g(int) を呼びます。 d.g('a'); // B::g(char) を呼びます。 b.h(1); // B::h(int) を呼びます。 d.h(1); // D::h(int) を呼びます。 }
出力:
D::f D::f D::g B::g B::h D::h
継承コンストラクタusing 宣言が定義中のクラスの直接の基底のコンストラクタを参照する場合 (例えば using Base::Base;)、その基底のすべてのコンストラクタ (メンバアクセスを無視します) が、派生クラスを初期化するときのオーバーロード解決に対して可視になります。 オーバーロード解決が継承コンストラクタを選択する場合、もし対応する基底クラスのオブジェクトを構築するために使用するときに可視であるならば、それはアクセス可能になります。 それを導入した using 宣言のアクセス可能性は無視されます。 そのような派生クラスのオブジェクトを初期化するときにオーバーロード解決が継承コンストラクタのいずれかを選択する場合、そのコンストラクタの継承元の struct B1 { B1(int, ...) { } }; struct B2 { B2(double) { } }; int get(); struct D1 : B1 { using B1::B1; // B1(int, ...) を継承します。 int x; int y = get(); }; void test() { D1 d(2, 3, 4); // OK、 B1 が B1(2, 3, 4), を呼ぶことによって初期化され、 // それから d.x がデフォルト初期化され (何の初期化も行われません)、 // それから d.y が get() を呼ぶことによって初期化されます。 D1 e; // エラー、 D1 はデフォルトコンストラクタを持ちません。 } struct D2 : B2 { using B2::B2; // B2(double) を継承します。 B1 b; }; D2 f(1.0); // エラー、 B1 はデフォルトコンストラクタを持ちません。 struct W { W(int); }; struct X : virtual W { using W::W; // W(int) を継承します。 X() = delete; }; struct Y : X { using X::X; }; struct Z : Y, virtual W { using Y::Y; }; Z z(0); // OK、 Y の初期化は X のデフォルトコンストラクタを呼びません。 コンストラクタを型 B の複数の基底クラス部分オブジェクトから継承した場合、プログラムは ill-formed です (多重継承した非静的メンバ関数と同様です)。 struct A { A(int); }; struct B : A { using A::A; }; struct C1 : B { using B::B; }; struct C2 : B { using B::B; }; struct D1 : C1, C2 { using C1::C1; using C2::C2; }; D1 d1(0); // ill-formed、コンストラクタが B の異なる基底部分オブジェクトから継承されています。 struct V1 : virtual B { using B::B; }; struct V2 : virtual B { using B::B; }; struct D2 : V1, V2 { using V1::V1; using V2::V2; }; D2 d2(0); // OK、 B の部分オブジェクトはひとつだけです。 // これは仮想基底クラス B を初期化し // (それによって基底クラス A が初期化されます)、 // それから基底クラス V1 および V2 を、デフォルト化された // デフォルトコンストラクタで行われたかのように初期化します。 他のあらゆる非静的メンバ関数に対する using 宣言の場合と同様に、継承したコンストラクタが struct B1 { B1(int); }; struct B2 { B2(int); }; struct D2 : B1, B2 { using B1::B1; using B2::B2; D2(int); // OK、 D2::D2(int) は B1::B1(int) と B2::B2(int) の両方を隠蔽します。 }; D2 d2(0); // D2::D2(int) を呼びます。 |
(C++11以上) |
[編集] ノート
宣言スコープに転送されるのは using 宣言で明示的に言及された名前だけです。 特に、列挙型の名前が using 宣言されたとき、その列挙子は転送されません。
using 宣言は名前空間、スコープ付き列挙子、基底クラスのデストラクタ、またはユーザ定義変換関数のためのメンバテンプレートの特殊化を参照することはできません。
using 宣言はメンバテンプレートの特殊化を指定することはできません (template-id は文法的に使用できません)。
struct B { template<class T> void f(); }; struct D : B { using B::f; // OK、テンプレートを指定します。 // using B::f<int>; // エラー、テンプレートの特殊化は指定できません。 void g() { f<int>(); } };
using 宣言は依存メンバテンプレートの名前をテンプレート名として導入するために使用することもできません (依存名に対する template
曖昧性解消子は使用できません)。
template<class X> struct B { template<class T> void f(T); }; template<class Y> struct D : B<Y> { // using B<Y>::template f; // エラー、曖昧性解消子は使用できません。 using B<Y>::f; // コンパイルできますが、 f はテンプレート名ではありません。 void g() { // f<int>(0); // エラー、 f がテンプレート名であることは判らないため、 // < によってテンプレート引数リストが開始されません。 f(0); // OK。 } };
using 宣言が派生クラスのコピー代入またはムーブ代入演算子にたまたま一致するシグネチャを持つ基底クラスの代入演算子を派生クラスに持ち込んだ場合、その演算子は派生クラスの暗黙に宣言されたコピー/ムーブ代入演算子によって隠蔽されます。 派生クラスのコピー/ムーブコンストラクタにたまたま一致する基底クラスのコンストラクタを継承する using 宣言にも同じルールが適用されます。 (C++11以上)
継承コンストラクタのセマンティクスは C++11 に対する欠陥報告によって遡及的に変更されました。 以前は、合成されたコンストラクタ宣言の集合を派生クラスに注入させる継承コンストラクタの宣言 (これは冗長な引数のコピー/ムーブを発生させます) は、 SFINAE の一部の形式と問題のある相互作用を発生させ、一部のケースでは主要な ABI で実装不可能となり得ました。 古いコンパイラは未だに以前のセマンティクスを実装しているかもしれません。
|
(C++11以上) |
using 宣言内のパック展開は可変長な基底のオーバーロードされたメンバを露出するクラスを再帰なしで形成することを可能とします。 template <typename... Ts> struct Overloader : Ts... { using Ts::operator()...; // すべての基底から operator() を露出させます。 }; template <typename... T> Overloader(T...) -> Overloader<T...>; // C++17 の推定ガイド。 int main() { auto o = Overloader{ [] (auto const& a) {std::cout << a;}, [] (float f) {std::cout << std::setprecision(3) << f;} }; } |
(C++17以上) |
[編集] 欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
DR | 適用先 | 発行時の動作 | 正しい動作 |
---|---|---|---|
P0136R1 | C++11 | inheriting constructor declaration injects additional constructors in the derived class | causes base class constructors to be found by name lookup |