Espacios de nombres
Variantes
Acciones

Declaración using

De cppreference.com
< cpp‎ | language
 
 
 
 

Introduce un nombre que se define en otro lado dentro de la región declarativa donde aparece esta declaración using.

using typename(opcional) especificador-de-nombre-anidado id-no-calificado ; (hasta C++17)
using lista-de-declaradores ; (desde C++17)
typename - La palabra clave typename puede ser usada como sea necesario para resolver nombres dependientes cuando la declaración using introduce un tipo de miembro de una clase base en una plantilla de clase.
especificador-de-nombre-anidado - Una secuencia de nombres y operadores de resolución de ámbito ::, que terminan con el operador de resolución de ámbito. Un solo :: se refiere al espacio de nombres global.
id-no-calificado - Una expresión-id
lista-de-declaradores - Una lista separada por comas de uno o más declaradores de typename(opcional) especificador-de-nombre-anidado id-no-calificado. Algunos o todos los declaradores pueden estar seguidos de puntos suspensivos ... para indicar expansión de paquete de parámetros

Contenido

[editar] Explicación

Las declaraciones using pueden usarse para introducir miembros de espacios de nombres en otros espacios de nombres y ámbitos de bloque, o para introducir miembros de una clase base en definiciones de una clase derivada, o para introducir enumeradores en ámbitos de espacios de nombres, bloques y clases (desde C++20).

Una declaración using con más de un declarador using es equivalente a la secuencia correspondiente de declaraciones using con un declarador using.

(desde C++17)

[editar] En ámbito de espacio de nombres y de bloque

Una declaración using introduce un miembro de otro espacio de nombres dentro del ámbito de espacio de nombres actual o de bloque actual.

#include <iostream>
#include <string>
 
using std::string; // introduce std::string en el espacio de nombres global
 
int main()
{
    string str = "Ejemplo";
    using std::cout; // introduce std::cout en el ámbito de la función
    cout << str;
}

Véase Espacio de nombres para más detalles.

[editar] En la definición de una clase

Una declaración using introduce un miembro de una clase base en la definición de la clase derivada, con el propósito de exponer un miembro protegido de la base como un miembro público de la derivada. En este caso, el especificador-de-nombre-anidado tiene que nombrar una de las clases base de la clase derivada que se está definiendo. Si el nombre es el nombre de una función miembro sobrecargada de la clase base, todas las funciones miembro con ese nombre se introducen. 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 reemplaza (no tiene conflicto) al miembro que se introduce de 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 protegida
    typedef int tipo_valor;
};
 
struct D : B 
{
    using B::m; // D::m es pública
    using B::tipo_valor; // D::tipo_valor es pública
 
    using B::f;
    void f(int) { std::cout << "D::f\n"; } // D::f(int) reemplaza a B::f(int), ya que B::f(int) es virtual
 
    using B::g;
    void g(int) { std::cout << "D::g\n"; } // tanto g(int) como g(char) son visibles
                                           // como miembros de D
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) oculta a B::h(int), ya que B::h(int) no es virtual
};
 
int main()
{
    D d;
    B& b = d;
 
//    b.m = 2; // ERROR, B::m es protegida
d.m = 1; // la B::m protegida puede accederse como D::m pública
 
    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, visible 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

Heredar constructores

Si la declaración using se refiere a un constructor de una base directa de la clase que se define (p. ej., using Base::Base;), todos los constructores de esa base (ignorando acceso de miembros) se hacen visibles para la resolución de sobrecarga cuando se inicialice la clase derivada.

Si la resolución de sobrecarga selecciona un constructor heredado, es accesible si hubiera sido accesible cuando se usó para construir un objeto de la clase base correspondiente: se ignora la accesibilidad de la declaración using que la introdujo.


Si la resolución de sobrecarga selecciona uno de los constructores heredados al inicializar un objeto de tal clase derivada, entonces el subobjeto Base subobject del cual se heredó el constructor, se inicializa usando el constructor heredado, y todos las otras bases y miembros de Derivada se inicializan como si se hubiera invocado el constructor por defecto (si se suministraron, se usan los inicializadores por defecto de los miembros, de otra manera la inicialización por defecto tiene lugar). Toda la inicialización se trata como una sola llamada de función: la inicialización de los parámetros del constructor heredado está secuenciado-antes que 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 a B1(int, ...)
  int x;
  int y = get();
};
 
void test() 
{
  D1 d(2, 3, 4); // de acuerdo: B1 se inicializa llamando a B1(2, 3, 4),
                 // entonces d.x se inicializa por defecto (no se ejecuta 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 a 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 a W(int)
    X() = delete;
};
 
struct Y : X 
{
    using X::X;
};
 
struct Z : Y, virtual W 
{
    using Y::Y;
};
 
Z z(0); // de acuerdo: la inicialización de Y no invoca al constructor por defecto de X

Si el constructor se heredó de múltiples subobjetos de clases base de tipo B, el programa está mal formado, similar a 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 de 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); // de acuerdo: 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 marcado con = default

Similar a las declaraciones using para cualquier otra función miembro no estática, si el constructor heredado coincide con la signatura de uno de los constructores de Derivada, se esconde de la búsqueda por la versión encontrada en Derivada. Si sucede que uno de los contructores heredados de Base tiene la signatura que coincide con el constructor de copia/movimiento de Derivada, no se previene la generación implícita del constructor de copia/movimiento de Derivada (que a su vez esconde 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);   // de acuerdo: D2::D2(int) esconde ambos: B1::B1(int) y B2::B2(int)
};
 
D2 d2(0);    // llama a D2::D2(int)


Dentro de una clase con plantilla, si una declaración using se refiere a un nombre dependiente, se considera que nombra a un constructor si el especificador-de-nombre-anidado tiene un nombre terminal que es mismo que el id-no-calificado.

template<class T>
struct A : T
{
    using T::T; // Correcto, hereda constructores de T
};
 
template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // Correcto, hereda 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 de ámbito

Como añadido a miembros de otro espacio de nombre y de clases base, la declaración using también puede introducir enumeradores de enumeraciones en los ámbitos de un espacio de nombres, bloque y clase.

También se puede usar una declaración using con enumeradores sin ámbito.

enum class boton { arriba, abajo };
 
struct S
{
    using boton::arriba;
    boton b = arriba; // correcto
};
 
using boton::abajo;
constexpr boton no_arriba = abajo; // correcto
 
constexpr auto obtener_boton(bool esta_arriba)
{
    using boton::arriba, boton::abajo;
    return esta_arriba ? arriba : abajo; // correcto
}
 
enum sinambito { val };
using sinambito::val; // correcto, aunque innecesario
(desde C++20)

[editar] Notas

Solamente 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 usando una declaración using.

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

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

struct B 
{ 
    template<class T> void f(); 
};
 
struct D : B 
{
      using B::f;      // de acuerdo: da nombre a una plantilla
//    using B::f<int>; // ERROR: da nombre a una especialización de plantilla
 
      void g() { f<int>(); }
};

Una declaración using tampoco se puede usar para introducir el nombre de una plantilla miembro dependiente como un desambiguador de nombre-de-plantilla (no se permite el desambiguador de template para nombres dependientes).

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 se permite
    using B<Y>::f;            // compila, pero f no es un nombre-de-plantilla
 
    void g() 
    {
//      f<int>(0);            // ERROR: f no se sabe que sea un nombre de plantilla,
                            // así que el signo menor que,  <, no empieza un lista de argumentos de plantilla
        f(0);                 // de acuerdo
    }   
};

Si una declaración using trae consigo el operador de asignación de la clase base a la clase derivada, y cuya signatura coincide con el operador de asignación de copia o el operador de asignación de movimiento de la clase derivada, ese operador se oculta por el operador de asignación de copia/movimiento implícitamente declarado de la clase derivada. Lo mismo se aplica a una declaración using que hereda un constructor de una clase base que coincide con el constructor de copia/movimiento de la clase derivada. (desde C++11)

La semántica de la herencia de constructores se cambió retroactivamente por un informe de defecto de C++11. Previamente, una declaración de constructor heredado ocasionaba que se inyectara un conjunto de declaraciones de constructor sintetizadas en la clase derivada, que a su vez ocasionaba copias/movimientos redundantes de argumentos, tenía interacciones problemáticas con algunas formas de SFINAE, y en algunos casos puede no ser implementable en ABIs importantes. Los compiladores más antiguos pueden todavía implementar la semántica previa.

Semántica antigua de constructor heredero

Si la declaración using se refiere a un constructor de una base directa de la clase que está siendo definida (p. ej., using Base::Base;), los constructores de esa clase base se heredan, de acuerdo a las siguientes reglas:

1) un conjunto de constructores herederos candidatos se compone de:
a) todos los constructores de no plantilla de la clase base (después de omitir los parámetros de puntos suspensivos, si es que los hay) (desde C++14);
b) para cada constructor con argumentos por defecto o el parámetro de puntos suspensivos, todas las signaturas de constructor que se forman al eliminar los puntos suspensivos y omitir los argumentos por defecto del final de las listas de argumentos uno por uno;
c) todos los constructores de plantilla de la clase base (después de omitir los parámetros de puntos suspensivos, si es que los hay) (desde C++14);
d) para cada constructor de plantilla con argumentos por defecto o el parámetro de puntos suspensivos, todas las signaturas de constructor que se forman al eliminar los puntos suspensivos y omitir los argumentos por defecto del final de las listas de argumentos uno por uno;
2) todos los constructores candidatos heredados que no son el constructor por defecto o el constructor de copia/movimiento y cuyas signaturas no coinciden con constructores definidos por el usuario en la clase derivada, son declarados implícitamente en la clase derivada. Los parámetros por defecto no se heredan:
struct B1 
{
    B1(int);
};
 
struct D1 : B1 
{
    using B1::B1;
    // El conjunto de constructores candidatos heredados es
    // 1. B1(const B1&)
    // 2. B1(B1&&)
    // 3. B1(int)
 
    // D1 tiene los siguientes constructores:
    // 1. D1() = delete
    // 2. D1(const D1&) 
    // 3. D1(D1&&)
    // 4. D1(int) <- heredado
};
 
struct B2 
{
    B2(int = 13, int = 42);
};
 
struct D2 : B2 
{
    using B2::B2;
    // El conjunto de constructores candidato heredados es
    // 1. B2(const B2&)
    // 2. B2(B2&&)
    // 3. B2(int = 13, int = 42)
    // 4. B2(int = 13)
    // 5. B2()
 
    // D2 tiene los siguientes constructores:
    // 1. D2()
    // 2. D2(const D2&)
    // 3. D2(D2&&)
    // 4. D2(int, int) <- heredado
    // 5. D2(int) <- heredado
};

Los constructores heredados son equivalentes a constructores definidos por el usuario con un cuerpo vacío y con una lista de inicializadores de miembros que consiste en un solo especificador-de-nombre-anidado, que reenvía todos sus argumentos al constructor de la clase base.

Tiene el mismo acceso que el constructor correspondiente de la clase base. Es constexpr si el constructor definido por el usuario hubiera satisfecho los requerimientos de constructor constexpr. Es borrado (deleted) si el constructor base correspondiente es borrado o si un constructor por defecto marcado con default fuera borrado (excepto que la construcción de la base cuyo constructor se está heredando no cuenta). Un constructor heredado no puede ser instanciado o especializado explícitamente.

Si dos declaraciones using heredan el constructor con la misma signatura (de dos clases base directas), el programa está mal formado.

(desde C++11)

La expansión de paquetes en declaraciones using hace posible formar una clase que expone los miembros sobrecargados de bases variádicas sin recursión:

template <typename... Ts>
struct Overloader : Ts... 
{
    using Ts::operator()...; // expone el operator() de todas las bases
};
 
template <typename... T>
Overloader(T...) -> Overloader<T...>; // guía de deducción de C++17, no necesario 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)

[editar] Informes de defectos

Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.

ID Aplicado a Comportamiento según lo publicado Comportamiento correcto
CWG 258 C++98 una función miembro no constante de una clase derivada puede
sobreescribir y/u ocultar una función miembro constante de su base
sobreescritura y ocultación requiere también
calificaciones cv para ser el mismo
P0136R1 C++11 La declaración del constructor heredero inyecta constructores
adicionales en la clase derivada
Ocasiona que los constructores de la clase base se encuentren
mediante la búsqueda de nombres