Declaración using
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 |
(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 constructoresSi la declaración 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
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 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)
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 ámbitoComo añadido a miembros de otro espacio de nombre y de clases base, la declaración También se puede usar una declaración 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.
|
(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 |