名前空間
変種
操作

using 宣言

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

他のどこかで定義された名前をこの 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 宣言のアクセス可能性は無視されます。

そのような派生クラスのオブジェクトを初期化するときにオーバーロード解決が継承コンストラクタのいずれかを選択する場合、そのコンストラクタの継承元の Base 部分オブジェクトはその継承したコンストラクタを用いて初期化され、その他のすべての基底と Derived のメンバはデフォルト化されたデフォルトコンストラクタによって行われたかのように初期化されます (もし提供されていればデフォルトメンバ初期化子が使用され、そうでなければデフォルト初期化を行います)。 初期化全体は単一の関数呼び出しとして扱われます。 継承したコンストラクタの引数の初期化はあらゆる基底または派生オブジェクトのメンバの初期化に対して先行配列されます。

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 宣言の場合と同様に、継承したコンストラクタが Derived のコンストラクタのいずれかのシグネチャと一致する場合、それは Derived のバージョンによって名前探索から隠蔽されます。 Base の継承したコンストラクタのいずれかが Derived のコピー/ムーブコンストラクタに一致するシグネチャをたまたま持っていた場合、それは Derived のコピー/ムーブコンストラクタの暗黙の生成を妨げません (using operator= と同様に、継承したバージョンを隠蔽します) 。

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 で実装不可能となり得ました。 古いコンパイラは未だに以前のセマンティクスを実装しているかもしれません。

古い継承コンストラクタの意味論

using 宣言が定義中のクラスの直接の基底のコンストラクタを参照する場合 (例えば using Base::Base;)、その基底クラスのコンストラクタは、以下のルールに従い、継承されます。

1) 候補継承コンストラクタの集合が以下から構成されます。
a) 基底クラスのすべての非テンプレートコンストラクタ (省略記号引数 (もしあれば) は削除されます) (C++14以上)
b) デフォルト引数または省略記号を持つコンストラクタそれぞれについて、省略記号およびデフォルト引数を引数リストの末尾から1つずつ削除することによって形成されるすべてのコンストラクタシグネチャ。
c) 基底クラスのすべてのコンストラクタテンプレート (省略記号 (もしあれば) は削除されます) (C++14以上)
d) デフォルト引数または省略記号を持つコンストラクタテンプレートそれぞれについて、省略記号およびデフォルト引数を引数リストの末尾から1つずつ削除することによって形成されるすべてのコンストラクタシグネチャ。
2) デフォルトコンストラクタでもコピ/ムーブコンストラクタでもなく、シグネチャが派生クラスのユーザ定義コンストラクタと一致しない、すべての候補継承コンストラクタが、派生クラスで暗黙に宣言されます。 デフォルト引数は継承されません。
struct B1 {
    B1(int);
};
struct D1 : B1 {
    using B1::B1;
// 候補継承コンストラクタの集合は以下です。
// 1. B1(const B1&)
// 2. B1(B1&&)
// 3. B1(int)
 
// D1 は以下のコンストラクタを持ちます。
// 1. D1() = delete
// 2. D1(const D1&) 
// 3. D1(D1&&)
// 4. D1(int) ← 継承したもの
};
 
struct B2 {
    B2(int = 13, int = 42);
};
struct D2 : B2 {
    using B2::B2;
// 候補継承コンストラクタの集合は以下です。
// 1. B2(const B2&)
// 2. B2(B2&&)
// 3. B2(int = 13, int = 42)
// 4. B2(int = 13)
// 5. B2()
 
// D2 は以下のコンストラクタを持ちます。
// 1. D2()
// 2. D2(const D2&)
// 3. D2(D2&&)
// 4. D2(int, int) ← 継承したもの
// 5. D2(int) ← 継承したもの
};

継承コンストラクタは、空の本体と、すべての引数を基底クラスのコンストラクタに転送する単一の nested-name-specifier から構成されるメンバ初期化子リストを持つ、ユーザ定義コンストラクタと、同等です。

継承コンストラクタは、対応する基底のコンストラクタと同じアクセスを持ちます。 継承コンストラクタは、同等なユーザ定義コンストラクタが constexpr コンストラクタの要件を満たすであろうならば、 constexpr です。 継承コンストラクタは、対応する基底のコンストラクタが削除されているか、デフォルト化されたデフォルトコンストラクタが削除されるであろう (ただし継承するコンストラクタの基底の構築は考慮しません) ならば、削除されます。 継承コンストラクタは、明示的実体化や明示的特殊化はできません。

2つの using 宣言が同じシグネチャのコンストラクタを (2つの直接の基底クラスから) 継承した場合、プログラムは ill-formed です。

(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