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

Introduce un nombre que está definido en otro lugar en la región declarativa donde aparece esta declaración using. Consulte using enum y (since C++20) using namespace para otras declaraciones relacionadas.

using typename (opcional) especificador-de-nombre-anidado identificador-no-calificado ; (hasta C++17)
using lista-de-declaradores ; (desde C++17)
typename - la palabra clave typename puede utilizarse según sea necesario para resolver nombres dependientes , cuando la declaración using introduce un tipo miembro de una clase base en una plantilla de clase
nested-name-specifier - una secuencia de nombres y operadores de resolución de ámbito :: , que termina con un operador de resolución de ámbito. Un único :: se refiere al espacio de nombres global.
unqualified-id - una expresión de identificación
declarator-list - lista separada por comas de uno o más declaradores del typename (opcional) nested-name-specifier unqualified-id . Algunos o todos los declaradores pueden ir seguidos de puntos suspensivos ... para indicar expansión de paquete

Contenidos

Explicación

Las declaraciones using pueden utilizarse para introducir miembros de un espacio de nombres en otros espacios de nombres y ámbitos de bloque, o para introducir miembros de una clase base en definiciones de clases derivadas , o para introducir enumerators en espacios de nombres, ámbitos de bloque y ámbitos de clase (since C++20) .

Una declaración using con más de un using-declarator es equivalente a una secuencia correspondiente de declaraciones using con un using-declarator.

(since C++17)

En ámbito de namespace y bloque

Using-declarations introducen un miembro de otro espacio de nombres en el espacio de nombres actual o ámbito de bloque.

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

Consulte namespace para más detalles.

En definición de clase

La declaración using introduce un miembro de una clase base en la definición de la clase derivada, como para exponer un miembro protegido de la base como miembro público de la derivada. En este caso, nested-name-specifier debe nombrar una clase base de la que se está definiendo. Si el nombre es el nombre de una función miembro sobrecargada de la clase base, todas las funciones miembro de la clase base con ese nombre son introducidas. Si la clase derivada ya tiene un miembro con el mismo nombre, lista de parámetros y calificaciones, el miembro de la clase derivada oculta o sobrescribe (no entra en conflicto con) el miembro que se introduce desde la clase base.

#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 es protegido
    typedef int value_type;
};
struct D : B
{
    using B::m;          // D::m es público
    using B::value_type; // D::value_type es público
    using B::f;
    void f(int) override { std::cout << "D::f\n"; } // D::f(int) sobreescribe B::f(int)
    using B::g;
    void g(int) { std::cout << "D::g\n"; } // tanto g(int) como g(char) son visibles
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) oculta B::h(int)
};
int main()
{
    D d;
    B& b = d;
//  b.m = 2;  // Error: B::m es protegido
    d.m = 1;  // B::m protegido es accesible como D::m público
    b.f(1);   // llama a f() derivada
    d.f(1);   // llama a f() derivada
    std::cout << "----------\n";
    d.g(1);   // llama a g(int) derivada
    d.g('a'); // llama a g(char) base, expuesta mediante using B::g;
    std::cout << "----------\n";
    b.h(1);   // llama a h() base
    d.h(1);   // llama a h() derivada
}

Salida:

D::f
D::f
----------
D::g
B::g
----------
B::h
D::h

Herencia de constructores

Si la using-declaration se refiere a un constructor de una base directa de la clase que se está definiendo (por ejemplo using Base :: Base ; ), todos los constructores de esa base (ignorando el acceso de miembro) se hacen visibles para la resolución de sobrecarga al inicializar la clase derivada.

Si la resolución de sobrecarga selecciona un constructor heredado, es accesible si sería accesible cuando se utiliza para construir un objeto de la clase base correspondiente: se ignora la accesibilidad de la declaración using que lo introdujo.

Si la resolución de sobrecarga selecciona uno de los constructores heredados al inicializar un objeto de dicha clase derivada, entonces el Base subobjeto del cual se heredó el constructor se inicializa utilizando el constructor heredado, y todas las demás bases y miembros de Derived se inicializan como si fuera por el constructor predeterminado por defecto (se utilizan los inicializadores de miembros predeterminados si se proporcionan, de lo contrario tiene lugar la inicialización predeterminada). Toda la inicialización se trata como una única llamada de función: la inicialización de los parámetros del constructor heredado está secuenciada antes de la inicialización de cualquier base o miembro del objeto derivado.

struct B1 { B1(int, ...) {} };
struct B2 { B2(double)   {} };
int get();
struct D1 : B1
{
    using B1::B1; // hereda B1(int, ...)
    int x;
    int y = get();
};
void test()
{
    D1 d(2, 3, 4); // OK: B1 se inicializa llamando a B1(2, 3, 4),
                   // luego d.x se inicializa por defecto (no se realiza inicialización),
                   // luego d.y se inicializa llamando a get()
    D1 e;          // Error: D1 no tiene constructor por defecto
}
struct D2 : B2
{
    using B2::B2; // hereda B2(double)
    B1 b;
};
D2 f(1.0); // error: B1 no tiene constructor por defecto
struct W { W(int); };
struct X : virtual W
{
    using W::W; // hereda W(int)
    X() = delete;
};
struct Y : X
{
    using X::X;
};
struct Z : Y, virtual W
{
    using Y::Y;
};
Z z(0); // OK: la inicialización de Y no invoca el constructor por defecto de X

Si el subobjeto de la clase base Base no va a ser inicializado como parte del objeto Derived (es decir, Base es una clase base virtual de Derived , y el objeto Derived no es el objeto más derivado ), se omite la invocación del constructor heredado, incluyendo la evaluación de cualquier argumento:

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()) {} // CORRECTO
};
struct C : B {};
void foo()
{
    C c; // "bar" no se invoca, porque el subobjeto V
         // no se inicializa como parte de B
         // (el subobjeto V se inicializa como parte de C,
         //  porque "c" es el objeto más derivado)
}

Si el constructor fue heredado de múltiples subobjetos de clase base de tipo Base , el programa está mal formado, similar a las funciones miembro no estáticas heredadas múltiples veces:

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); // mal formado: constructor heredado de diferentes subobjetos base 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); // correcto: solo hay un subobjeto B.
          // Esto inicializa la clase base virtual B,
          //   que inicializa la clase base A
          // luego inicializa las clases base V1 y V2
          //   como si fuera por un constructor por defecto predeterminado

Al igual que con las declaraciones using para cualquier otra función miembro no estática, si un constructor heredado coincide con la firma de uno de los constructores de Derived , queda oculto de la búsqueda por la versión encontrada en Derived . Si uno de los constructores heredados de Base coincide por casualidad con la firma de un constructor de copia/movimiento de Derived , no impide la generación implícita del constructor de copia/movimiento de Derived (que luego oculta la versión heredada, similar a 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) oculta tanto B1::B1(int) como B2::B2(int)
};
D2 d2(0);    // llama a D2::D2(int)

Dentro de una clase plantilla , si una declaración using se refiere a un nombre dependiente , se considera que nombra un constructor si el nested-name-specifier tiene un nombre terminal que es el mismo que el unqualified-id .

template<class T>
struct A : T
{
    using T::T; // OK, hereda los constructores de T
};
template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // OK, hereda los constructores de A<U>
    using T::A;    // no hereda el constructor de T
                   // aunque T puede ser una especialización de A<>
};
(desde C++11)


Introducción de enumeradores con ámbito

Además de los miembros de otro espacio de nombres y los miembros de clases base, la declaración using también puede introducir enumeradores de enumeraciones en ámbitos de espacio de nombres, bloque y clase.

Una declaración using también puede usarse con enumeradores sin ámbito.

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
(desde C++20)

Notas

Solo el nombre mencionado explícitamente en la declaración using se transfiere al ámbito declarativo: en particular, los enumeradores no se transfieren cuando el nombre del tipo de enumeración se declara mediante using.

Una declaración using no puede referirse a un espacio de nombres , a un enumerador con ámbito (until C++20) , a un destructor de una clase base o a una especialización de una plantilla de miembro para una función de conversión definida por el usuario.

Una declaración using no puede nombrar una especialización de plantilla de miembro ( template-id no está permitido por la gramática):

struct B
{
    template<class T>
    void f();
};
struct D : B
{
    using B::f;      // OK: nombra una plantilla
//  using B::f<int>; // Error: nombra una especialización de plantilla
    void g() { f<int>(); }
};

Una declaración using tampoco puede utilizarse para introducir el nombre de una plantilla de miembro dependiente como un template-name (el desambiguador template para dependent names no está permitido).

template<class X>
struct B
{
    template<class T>
    void f(T);
};
template<class Y>
struct D : B<Y>
{
//  using B<Y>::template f; // Error: desambiguador no permitido
    using B<Y>::f;          // compila, pero f no es un nombre de plantilla
    void g()
    {
//      f<int>(0);          // Error: f no se reconoce como nombre de plantilla,
                            // por lo que < no inicia una lista de argumentos de plantilla
        f(0);               // Correcto
    }   
};

Si una declaración using introduce el operador de asignación de la clase base en la clase derivada, cuya firma coincide con el operador de copia-asignación o movimiento-asignación de la clase derivada, ese operador es ocultado por el operador de copia/movimiento asignación declarado implícitamente de la clase derivada. Lo mismo aplica para una declaración using que hereda un constructor de clase base que coincide con el constructor de copia/movimiento de la clase derivada (desde C++11) .

La semántica de los constructores heredados fue modificada retroactivamente por un informe de defectos contra C++11 . Anteriormente, una declaración de constructor heredado causaba que un conjunto de declaraciones de constructores sintetizados se inyectaran en la clase derivada, lo que provocaba copias/movimientos redundantes de argumentos, tenía interacciones problemáticas con algunas formas de SFINAE, y en algunos casos podía ser no implementable en ABIs principales. Los compiladores más antiguos aún pueden implementar la semántica anterior.

Semántica antigua de constructores heredados

Si la using-declaration se refiere a un constructor de una base directa de la clase que se está definiendo (ej. using Base :: Base ; ), los constructores de esa clase base se heredan, de acuerdo con las siguientes reglas:

1) Un conjunto de constructores heredados candidatos se compone de
a) Todos los constructores no plantilla de la clase base (después de omitir parámetros de elipsis, si los hay) (desde C++14)
b) Para cada constructor con argumentos predeterminados o el parámetro de elipsis, todas las firmas de constructor que se forman al eliminar la elipsis y omitir argumentos predeterminados de los extremos de las listas de argumentos uno por uno
c) Todas las plantillas de constructor de la clase base (después de omitir parámetros de elipsis, si los hay) (desde C++14)
d) Para cada plantilla de constructor con argumentos predeterminados o la elipsis, todas las firmas de constructor que se forman al eliminar la elipsis y omitir argumentos predeterminados de los extremos de las listas de argumentos uno por uno
2) Todos los constructores heredados candidatos que no son el constructor por defecto o el constructor de copia/movimiento y cuyas firmas no coinciden con constructores definidos por el usuario en la clase derivada, se declaran implícitamente en la clase derivada. Los parámetros predeterminados no se heredan:
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
};

Los constructores heredados son equivalentes a constructores definidos por el usuario con un cuerpo vacío y con una lista de inicialización de miembros que consiste en un único nested-name-specifier , que reenvía todos sus argumentos al constructor de la clase base.

Tiene el mismo acceso que el constructor base correspondiente. Es constexpr si el constructor definido por el usuario hubiera satisfecho los requisitos de constructor constexpr . Está eliminado si el constructor base correspondiente está eliminado o si un constructor por defecto predeterminado estaría eliminado (excepto que la construcción de la base cuyo constructor se está heredando no cuenta). Un constructor heredado no puede ser instanciado explícitamente o especializado explícitamente.

Si dos using-declarations heredan el constructor con la misma firma (desde dos clases base directas), el programa está mal formado.

Una plantilla de constructor heredado no debe ser instanciada explícitamente o especializada explícitamente .

(desde C++11)

Las expansiones de paquetes en las declaraciones using permiten formar una clase que expone miembros sobrecargados de bases variádicas sin recursión:

template<typename... Ts>
struct Overloader : Ts...
{
    using Ts::operator()...; // expone operator() de cada base
};
template<typename... T>
Overloader(T...) -> Overloader<T...>; // guía de deducción C++17, no necesaria en C++20
int main()
{
    auto o = Overloader{ [] (auto const& a) {std::cout << a;},
                         [] (float f) {std::cout << std::setprecision(3) << f;} };
}
(desde C++17)
Macro de prueba de características Valor Std Característica
__cpp_inheriting_constructors 200802L (C++11) Constructores heredados
201511L (C++17)
(DR11)
Reformulación de constructores heredados
__cpp_variadic_using 201611L (C++17) Expansiones de paquetes en declaraciones using

Palabras clave

using

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Se aplica a Comportamiento publicado Comportamiento correcto
CWG 258 C++98 una función miembro no-const de una clase derivada puede
anular y/o ocultar una función miembro const de su base
la anulación y ocultación también requieren
que las calificaciones cv sean las mismas
CWG 1738 C++11 no estaba claro si está permitido
instanciar explícitamente o especializar explícitamente
especializaciones de plantillas de constructores heredados
prohibido
CWG 2504 C++11 el comportamiento de heredar constructores
de clases base virtuales no estaba claro
aclarado
P0136R1 C++11 la declaración de constructor heredado inyecta
constructores adicionales en la clase derivada
hace que los constructores de la clase base
sean encontrados mediante búsqueda por nombre

Referencias

  • Estándar C++23 (ISO/IEC 14882:2024):
  • 9.9 La declaración using [namespace.udecl]
  • Estándar C++20 (ISO/IEC 14882:2020):
  • 9.9 La declaración using [namespace.udecl]
  • Estándar C++17 (ISO/IEC 14882:2017):
  • 10.3.3 La declaración using [namespace.udecl]
  • Estándar C++14 (ISO/IEC 14882:2014):
  • 7.3.3 La declaración using [namespace.udecl]
  • Estándar C++11 (ISO/IEC 14882:2011):
  • 7.3.3 La declaración using [namespace.udecl]
  • Estándar C++03 (ISO/IEC 14882:2003):
  • 7.3.3 La declaración using [namespace.udecl]
  • Estándar C++98 (ISO/IEC 14882:1998):
  • 7.3.3 La declaración using [namespace.udecl]