Spec-Zone.ru › C++

Объявление using

Вводит имя, определённое где-то ещё, в область объявления, где появляется это объявление using.

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

Объяснение

Объявления 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 вводит член базового класса в определение производного класса, например, для раскрытия защищённого члена базового как публичного члена производного. В этом случае спецификатор_вложенного_имени должен называть базовый класс определённого. Если имя является именем перегруженной функции-члена базового класса, все функции-члены базового класса с этим именем вводятся. Если у производного класса уже есть член с таким же именем, списком параметров и квалификациями, член производного класса скрывает или переопределяет (не конфликтует) член, который введён из базового класса.

#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-объявление относится к зависимому имени, оно считается именем конструктора, если спецификатор_вложенного_имени имеет конечное имя, которое такое же, как неквалифицированный_идентификатор.

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-name (дизъюнктор 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).

END_OF_DOCUMENT_MARKER

Семантика наследования конструкторов была ретроактивно изменена отчётом о дефекте по C++11. Ранее, объявление наследуемого конструктора приводило к вводу в производный класс набора синтезированных объявлений конструкторов, что вызывало избыточные копирования/перемещения аргументов, имело проблемные взаимодействия с некоторыми формами SFINAE, а в некоторых случаях было нереализуемо на основных ABIs. Более старые компиляторы могут по-прежнему реализовывать предыдущие семантики.

Старая семантика наследования конструкторов

Если объявление 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++11)
(DR)
Переформулировка наследуемых конструкторов
__cpp_variadic_using 201611L (C++17) Расширения пакетов в объявлениях using

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

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

DR Применимо к Поведение, как опубликовано Правильное поведение
CWG 258 C++98 неконстантный член-функция производного класса может
переопределять и/или скрывать константную член-функцию базового класса
переопределение и скрытие также требуют
cv-квалификации быть одинаковыми
CWG 1738 C++11 не было ясно, разрешено ли явно инстанцировать или явно специализировать
специализации шаблонов наследуемых конструкторов
запрещено
CWG 2504 C++11 поведение наследуемых конструкторов
от виртуальных базовых классов было неясно
уточнено
P0136R1 C++11 объявление наследуемого конструктора вводит
дополнительные конструкторы в производном классе
приводит к тому, что конструкторы базового класса
находятся с помощью поиска по имени

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

Spec-Zone.ru

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