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 и (since C++20) using namespace для других связанных деклараций.

using typename (необязательно) nested-name-specifier unqualified-id ; (до C++17)
using declarator-list ; (начиная с C++17)
typename - ключевое слово typename может использоваться при необходимости для разрешения зависимых имен , когда using-объявление вводит тип-член из базового класса в шаблон класса
nested-name-specifier - последовательность имен и операторов разрешения области видимости :: , заканчивающаяся оператором разрешения области видимости. Одиночный :: ссылается на глобальное пространство имен.
unqualified-id - id-выражение
declarator-list - разделенный запятыми список из одного или более деклараторов вида typename (опционально) nested-name-specifier unqualified-id . Некоторые или все деклараторы могут заканчиваться многоточием ... для указания раскрытия пакета

Содержание

Объяснение

Using-объявления могут использоваться для введения членов пространства имён в другие пространства имён и области видимости блоков, или для введения членов базового класса в определения производных классов , или для введения enumerators в пространства имён, блоки и области видимости классов (since 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 вводит член базового класса в определение производного класса, например, чтобы сделать защищенный член базового класса открытым членом производного. В этом случае 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) 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 является protected
    d.m = 1;  // protected B::m доступен как public 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 инициализируется вызовом 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

Если базовый подобъект класса 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); // корректно: существует только один подобъект 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)

Внутри шаблонного класса , если using-объявление ссылается на зависимое имя , оно считается именующим конструктор, если nested-name-specifier имеет терминальное имя, совпадающее с unqualified-id .

template<class T>
struct A : T
{
    using T::T; // OK, наследует конструкторы T
};
template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // OK, наследует конструкторы A<U>
    using T::A;    // не наследует конструктор T
                   // даже если T может быть специализацией A<>
};
(начиная с C++11)


Введение перечислителей с областью видимости

В дополнение к членам другого пространства имен и членам базовых классов, 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
(начиная с C++20)

Примечания

В декларативную область переносится только имя, явно указанное в using-объявлении: в частности, перечислители не переносятся, когда имя типа перечисления объявляется через using.

Объявление using не может ссылаться на пространство имён , на элемент ограниченного перечисления (до C++20) , на деструктор базового класса или на специализацию шаблона элемента для пользовательской функции преобразования.

Объявление 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-name (дизамбигуатор template для dependent names не допускается).

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) Всех нешаблонных конструкторов базового класса (после опускания параметров с многоточием, если есть) (since C++14)
b) Для каждого конструктора с аргументами по умолчанию или параметром с многоточием, всех сигнатур конструкторов, которые формируются путем удаления многоточия и опускания аргументов по умолчанию с концов списков аргументов по одному
c) Всех шаблонов конструкторов базового класса (после опускания параметров с многоточием, если есть) (since C++14)
d) Для каждого шаблона конструктора с аргументами по умолчанию или многоточием, всех сигнатур конструкторов, которые формируются путем удаления многоточия и опускания аргументов по умолчанию с концов списков аргументов по одному
2) Все кандидаты наследуемых конструкторов, которые не являются конструктором по умолчанию или конструктором копирования/перемещения и чьи сигнатуры не совпадают с пользовательскими конструкторами в производном классе, неявно объявляются в производном классе. Параметры по умолчанию не наследуются:
struct B1
{
    B1(int);
};
struct D1 : B1
{
    using B1::B1;
    // The set of candidate inherited constructors is 
    // 1. B1(const B1&)
    // 2. B1(B1&&)
    // 3. B1(int)
    // D1 has the following constructors:
    // 1. D1() = delete
    // 2. D1(const D1&) 
    // 3. D1(D1&&)
    // 4. D1(int) <- inherited
};
struct B2
{
    B2(int = 13, int = 42);
};
struct D2 : B2
{
    using B2::B2;
    // The set of candidate inherited constructors is
    // 1. B2(const B2&)
    // 2. B2(B2&&)
    // 3. B2(int = 13, int = 42)
    // 4. B2(int = 13)
    // 5. B2()
    // D2 has the following constructors:
    // 1. D2()
    // 2. D2(const D2&)
    // 3. D2(D2&&)
    // 4. D2(int, int) <- inherited
    // 5. D2(int) <- inherited
};

Наследуемые конструкторы эквивалентны пользовательским конструкторам с пустым телом и со списком инициализации членов , состоящим из одного nested-name-specifier , который перенаправляет все свои аргументы в конструктор базового класса.

Он имеет тот же доступ , что и соответствующий базовый конструктор. Он является constexpr , если пользовательский конструктор удовлетворял бы требованиям constexpr конструктора. Он удален, если соответствующий базовый конструктор удален или если конструктор по умолчанию, заданный по умолчанию, был бы удален (за исключением того, что конструирование базы, чей конструктор наследуется, не учитывается). Наследуемый конструктор не может быть явно инстанцирован или явно специализирован.

Если два using-объявления наследуют конструктор с одинаковой сигнатурой (от двух прямых базовых классов), программа некорректна.

Шаблон наследуемого конструктора не должен быть явно инстанцирован или явно специализирован .

(since C++11)

Pack expansions в 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;} };
}
(начиная с C++17)
Макрос тестирования возможностей Значение Стандарт Возможность
__cpp_inheriting_constructors 200802L (C++11) Наследование конструкторов
201511L (C++17)
(DR11)
Переформулировка наследования конструкторов
__cpp_variadic_using 201611L (C++17) Расширения пакетов в using -объявлениях

Ключевые слова

using

Отчеты о дефектах

Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.

DR Applied to Behavior as published Correct behavior
CWG 258 C++98 неконстантная функция-член производного класса может
переопределять и/или скрывать константную функцию-член своего базового класса
переопределение и сокрытие также требуют
одинаковых 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]