Namespaces
Variants

Using-declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

将其他地方定义的名称引入到当前 using 声明所在的声明区域。有关其他相关声明,请参阅 using enum (C++20 起) using namespace

using typename (可选) 嵌套名称说明符 非限定标识符 ; (C++17 前)
using 声明符列表 ; (C++17 起)
typename - 当 using 声明从基类向类模板引入成员类型时,可使用关键字 typename 来解析 依赖名称
nested-name-specifier - 由名称和作用域解析运算符 :: 组成的序列,以作用域解析运算符结尾。单个 :: 表示全局命名空间
unqualified-id - 一个 标识表达式
declarator-list - 由一或多个声明符组成的逗号分隔列表,形式为 typename (可选) nested-name-specifier unqualified-id 。部分或全部声明符后可接省略号 ... 以指示 包展开

目录

说明

using 声明可用于将命名空间成员引入其他命名空间和块作用域,或将基类成员引入派生类定义 ,或将 枚举项 引入命名空间、块及类作用域 (C++20 起)

具有多个 using 声明符的 using 声明等价于对应的单 using 声明符序列的 using 声明。

(since C++17)

在命名空间与块作用域中

Using-declarations 将其他命名空间的成员引入当前命名空间或块作用域。

#include <iostream>
#include <string>
using std::string;
int main()
{
    string str = "示例";
    using std::cout;
    cout << str;
}

参见 namespace 获取详细信息。

在类定义中

using 声明将基类的成员引入派生类定义中,例如将基类的受保护成员暴露为派生类的公开成员。这种情况下, 嵌套名限定符 必须指定当前定义类的某个基类。如果该名称是基类中重载成员函数的名称,则会引入基类中所有同名成员函数。若派生类已存在具有相同名称、参数列表和限定条件的成员,则派生类成员将隐藏或覆盖(而非冲突)从基类引入的成员。

#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 是受保护的
    typedef int value_type;
};
struct D : B
{
    using B::m;          // D::m 是公开的
    using B::value_type; // D::value_type 是公开的
    using B::f;
    void f(int) override { 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) 均可见
    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 是受保护的
    d.m = 1;  // 受保护的 B::m 可作为公开的 D::m 访问
    b.f(1);   // 调用派生类的 f()
    d.f(1);   // 调用派生类的 f()
    std::cout << "----------\n";
    d.g(1);   // 调用派生类的 g(int)
    d.g('a'); // 调用基类的 g(char),通过 using B::g 暴露
    std::cout << "----------\n";
    b.h(1);   // 调用基类的 h()
    d.h(1);   // 调用派生类的 h()
}

输出:

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(2, 3, 4) 初始化 B1,
                   // 然后 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 的默认构造函数

Base 基类子对象不作为 Derived 对象的一部分进行初始化(即 Base Derived 虚基类 ,且 Derived 对象不是 最终派生对象 ),则继承构造函数的调用(包括任何参数的求值)会被省略:

struct V
{
    V() = default;
    V(int);
};
struct Q { Q(); };
struct A : virtual V, Q
{
    using V::V;
    A() = delete;
};
int bar() { return 42; }
struct B : A
{
    B() : A(bar()) {} // OK
};
struct C : B {};
void foo()
{
    C c; // 不调用 “bar”,因为 V 子对象
         // 不作为 B 的一部分初始化
         //(V 子对象作为 C 的一部分初始化,
         //  因为 “c” 是最終派生对象)
}

若构造函数从多个 Base 类型的基类子对象继承,则程序非良构,类似于多重继承的非静态成员函数:

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); // 非良构:从不同的 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); }<span class="


引入作用域枚举项

除了其他命名空间的成员和基类的成员之外,using 声明还可以将 枚举 的枚举项引入命名空间、块作用域和类作用域。

using 声明也可用于无作用域枚举项。

enum class button { up, down };
struct S
{
    using button::up;
    button b = up; // OK
};
using button::down;
constexpr button non_up = down; // OK
constexpr auto get_button(bool is_up)
{
    using button::up, button::down;
    return is_up ? up : down; // OK
}
enum unscoped { val };
using unscoped::val; // OK, though needless
(since C++20)

注释

仅通过 using 声明明确提及的名称会被转入声明作用域:特别地,当枚举类型名被 using 声明时,其枚举项不会被转入。

using 声明不能引用命名空间 、作用域枚举项 (C++20 前) 、基类的析构函数或用户定义转换函数的成员模板特化。

using声明不能命名成员模板特化(语法不允许 模板ID ):

struct B
{
    template<class T>
    void f();
};
struct D : B
{
    using B::f;      // 正确:声明模板
//  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);               // 正确
    }   
};

如果 using 声明将基类赋值运算符引入派生类,且其签名恰好与派生类的复制赋值或移动赋值运算符匹配,则该运算符会被派生类隐式声明的复制/移动赋值运算符隐藏。 同样适用于继承基类构造函数的 using 声明,当该构造函数恰好与派生类复制/移动构造函数匹配时 (C++11 起)

继承构造函数的语义曾通过 针对C++11的缺陷报告 进行追溯性修改。早期版本中,继承构造函数声明会导致一组合成构造函数声明被注入派生类,这会引起冗余的参数拷贝/移动操作,与某些形式的SFINAE存在交互问题,且在部分主流ABI中无法实现。旧版本编译器可能仍沿用早期语义。

旧版继承构造函数语义

using声明 引用的是所定义类的直接基类的构造函数(例如 using Base :: Base ; ),则根据以下规则继承该基类的构造函数:

1) 候选继承构造函数 集合由以下组成:
a) 基类的所有非模板构造函数 (忽略省略号参数后) (C++14 起)
b) 对于含默认实参或省略号的构造函数,通过逐个移除参数列表末尾的省略号和默认实参所形成的所有构造函数签名
c) 基类的所有构造函数模板 (忽略省略号参数后) (C++14 起)
d) 对于含默认实参或省略号的构造函数模板,通过逐个移除参数列表末尾的省略号和默认实参所形成的所有构造函数签名
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) <- 继承
};

继承构造函数等效于具有空函数体、且其 成员初始化列表 仅包含单个 嵌套名说明符 的用户定义构造函数,该列表将其所有参数转发给基类构造函数。

访问权限 与对应基类构造函数相同。若用户定义构造函数满足 constexpr 构造函数要求,则其为 constexpr 构造函数。若对应基类构造函数被删除,或默认化的默认构造函数会被删除(但正在继承其构造函数的基类构造不计入),则该继承构造函数被删除。继承构造函数不能显式实例化或显式特化。

若两个using声明继承具有相同签名的构造函数(来自两个直接基类),则程序非良构。

继承构造函数模板不应被 显式实例化 显式特化

(C++11 起)

包展开 在 using 声明中使得无需递归即可形成暴露可变参数基类重载成员的类:

template<typename... Ts>
struct Overloader : Ts...
{
    using Ts::operator()...; // exposes operator() from every base
};
template<typename... T>
Overloader(T...) -> Overloader<T...>; // C++17 deduction guide, not needed in C++20
int main()
{
    auto o = Overloader{ [] (auto const& a) {std::cout << a;},
                         [] (float f) {std::cout << std::setprecision(3) << f;} };
}
(since C++17)
功能测试宏 标准 功能特性
__cpp_inheriting_constructors 200802L (C++11) 继承构造函数
201511L (C++17)
(DR11)
重述继承构造函数
__cpp_variadic_using 201611L (C++17) 参数包展开 using 声明中

关键词

using

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 应用于 发布时的行为 正确行为
CWG 258 C++98 派生类的非const成员函数可以
重写和/或隐藏其基类的const成员函数
重写和隐藏还需要
cv限定符相同
CWG 1738 C++11 未明确是否允许
显式实例化或显式特化
继承构造函数模板的特化
禁止
CWG 2504 C++11 从虚基类继承构造函数
的行为不明确
予以明确
P0136R1 C++11 继承构造函数声明会注入
额外的构造函数到派生类中
使基类构造函数
可通过名称查找找到

参考文献

  • C++23 标准 (ISO/IEC 14882:2024):
  • 9.9 using 声明 [namespace.udecl]
  • C++20 标准 (ISO/IEC 14882:2020):
  • 9.9 using 声明 [namespace.udecl]
  • C++17 标准 (ISO/IEC 14882:2017):
  • 10.3.3 using 声明 [namespace.udecl]
  • C++14 标准 (ISO/IEC 14882:2014):
  • 7.3.3 using 声明 [namespace.udecl]
  • C++11 标准 (ISO/IEC 14882:2011):
  • 7.3.3 using 声明 [namespace.udecl]
  • C++03 标准 (ISO/IEC 14882:2003):
  • 7.3.3 using 声明 [namespace.udecl]
  • C++98 标准 (ISO/IEC 14882:1998):
  • 7.3.3 using 声明 [namespace.udecl]