Spec-Zone.ru › C++

Объявление using

Вводит имя, определённое в другом месте, в область декларативной видимости, где появляется это объявление using. См. using enum и (начиная с 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 - выражение-идентификатор
declarator-list - разделённый запятыми список из одного или нескольких объявителей вида typename(необязательно) nested-name-specifier unqualified-id. За некоторыми или всеми объявителями может следовать многоточие ..., указывающее на раскрытие пакета

Пояснение

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

Объявление using с несколькими using-деклараторами эквивалентно соответствующей последовательности объявлений 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 is protected
    typedef int value_type;
};
 
struct D : B
{
    using B::m;          // D::m is public
    using B::value_type; // D::value_type is public

    using B::f;
    void f(int) override { std::cout << "D::f\n"; } // D::f(int) overrides B::f(int)

    using B::g;
    void g(int) { std::cout << "D::g\n"; } // both g(int) and g(char) are visible

    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) hides B::h(int)
};
 
int main()
{
    D d;
    B& b = d;
 
//  b.m = 2;  // Error: B::m is protected
    d.m = 1;  // protected B::m is accessible as public D::m
    
    b.f(1);   // calls derived f()
    d.f(1);   // calls derived f()
    std::cout << "----------\n";
    
    d.g(1);   // calls derived g(int)
    d.g('a'); // calls base g(char), exposed via using B::g;
    std::cout << "----------\n";
    
    b.h(1);   // calls base h()
    d.h(1);   // calls derived 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; // inherits B1(int, ...)
    int x;
    int y = get();
};

void test()
{
    D1 d(2, 3, 4); // OK: B1 is initialized by calling B1(2, 3, 4),
                   // then d.x is default-initialized (no initialization is performed),
                   // then d.y is initialized by calling get()
    
    D1 e;          // Error: D1 has no default constructor
}

struct D2 : B2
{
    using B2::B2; // inherits B2(double)
    B1 b;
};

D2 f(1.0); // error: B1 has no default constructor
struct W { W(int); };

struct X : virtual W
{
    using W::W; // inherits W(int)
    X() = delete;
};

struct Y : X
{
    using X::X;
};

struct Z : Y, virtual W
{
    using Y::Y;
};

Z z(0); // OK: initialization of Y does not invoke default constructor of 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” is not invoked, because the V subobject
         // is not initialized as part of B
         // (the V subobject is initialized as part of C,
         //  because “c” is the most derived object)
}

Если конструктор унаследован от нескольких подобъектов базового класса типа 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); // ill-formed: constructor inherited from different B base subobjects

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: there is only one B subobject.
          // This initializes the virtual B base class,
          //   which initializes the A base class
          // then initializes the V1 and V2 base classes
          //   as if by a defaulted default constructor

Как и в случае объявлений 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) hides both B1::B1(int) and B2::B2(int)
};
D2 d2(0);    // calls D2::D2(int)

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

template<class T>
struct A : T
{
    using T::T; // OK, inherits constructors of T
};

template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // OK, inherits constructors of A<U>
    using T::A;    // does not inherit constructor of T
                   // even though T may be a specialization of 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: names a template
//  using B::f<int>; // Error: names a template specialization
    
    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; // Error: disambiguator not allowed
    using B<Y>::f;          // compiles, but f is not a template-name
    
    void g()
    {
//      f<int>(0);          // Error: f is not known to be a template name,
                            // so < does not start a template argument list
        f(0);               // OK
    }   
};

Если объявление 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;
    
    // 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 наследуют конструктор с одной и той же сигнатурой (из двух прямых базовых классов), программа некорректна.

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

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

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

using

Отчёты об ошибках

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

DR Применяется к Поведение согласно опубликованной редакции Корректное поведение
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]

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/using_declaration

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API