¿Cuál es la lógica detrás de la palabra clave "usar" en C ++?

145

¿Cuál es la lógica detrás de la palabra clave "usar" en C ++?

Se usa en diferentes situaciones y estoy tratando de encontrar si todos tienen algo en común y hay una razón por la cual la palabra clave "usar" se usa como tal.

using namespace std; // to import namespace in the current namespace
using T = int; // type alias
using SuperClass::X; // using super class methods in derived class
usuario3111311
fuente
53
El comité estándar odia introducir nuevas palabras clave en la gramática de C ++.
interconexiones están hechas de catz
44
@tehinternetsismadeofcatz Si esa es la lógica realmente, discúlpeme, iré y me mataré ahora.
user3111311
62
@ user3111311: Reconoce las implicaciones de introducir nuevas palabras reservadas, ¿verdad? Significa que todo el código existente que los usó como nombres de identificadores repentinamente no se compila. Eso es MALO. Por ejemplo, hay mucho código C que no se puede compilar como C ++ porque contiene cosas como int class;. Sería aún peor si el código C ++ dejara de ser válido C ++ de repente.
Ben Voigt
77
@BenVoigt: El hecho de que el código C int class;no se compile como C ++ no es del todo malo. Se puede usar para garantizar que el código C se compilará como C. Es demasiado fácil olvidar que C y C ++ son dos lenguajes diferentes y, en términos prácticos, hay un código que es C válido y C ++ válido, pero con una semántica diferente.
Keith Thompson
1
En ese sentido, usingno es peor (o mejor) que static. En mi humilde opinión, el punto de no introducir nuevas palabras clave es muy importante como lo explican los internets que están hechos de catz y Ben Voigt.
Cassio Neri

Respuestas:

114

En C ++ 11, la usingpalabra clave cuando se usa para type aliases idéntica a typedef.

7.1.3.2

Un typedef-name también se puede introducir mediante una declaración de alias. El identificador que sigue a la palabra clave using se convierte en typedef-name y el atributo-specifier-seq opcional que sigue al identificador pertenece a ese typedef-name. Tiene la misma semántica que si fuera introducida por el especificador typedef. En particular, no define un nuevo tipo y no aparecerá en la identificación de tipo.

Bjarne Stroustrup ofrece un ejemplo práctico:

typedef void (*PFD)(double);    // C style typedef to make `PFD` a pointer to a function returning void and accepting double
using PF = void (*)(double);    // `using`-based equivalent of the typedef above
using P = [](double)->void; // using plus suffix return type, syntax error
using P = auto(double)->void // Fixed thanks to DyP

Antes de C ++ 11, la usingpalabra clave puede llevar las funciones miembro al alcance. En C ++ 11, ahora puede hacer esto para constructores (otro ejemplo de Bjarne Stroustrup):

class Derived : public Base { 
public: 
    using Base::f;    // lift Base's f into Derived's scope -- works in C++98
    void f(char);     // provide a new f 
    void f(int);      // prefer this f to Base::f(int) 

    using Base::Base; // lift Base constructors Derived's scope -- C++11 only
    Derived(char);    // provide a new constructor 
    Derived(int);     // prefer this constructor to Base::Base(int) 
    // ...
}; 

Ben Voight proporciona una razón bastante buena detrás de la razón de no introducir una nueva palabra clave o nueva sintaxis. El estándar quiere evitar romper el código antiguo tanto como sea posible. Por eso, en la documentación de propuestas verá secciones gusta Impact on the Standard, Design decisionsy cómo pueden afectar código antiguo. Hay situaciones en las que una propuesta parece una muy buena idea, pero podría no tener tracción porque sería demasiado difícil de implementar, demasiado confusa o contradiría el código antiguo.


Aquí hay un artículo antiguo de 2003 n1449 . La justificación parece estar relacionada con las plantillas. Advertencia: puede haber errores tipográficos debido a la copia desde PDF.

Primero consideremos un ejemplo de juguete:

template <typename T>
class MyAlloc {/*...*/};

template <typename T, class A>
class MyVector {/*...*/};

template <typename T>

struct Vec {
typedef MyVector<T, MyAlloc<T> > type;
};
Vec<int>::type p; // sample usage

El problema fundamental con este modismo, y el principal hecho motivador para esta propuesta, es que el modismo hace que los parámetros de la plantilla aparezcan en un contexto no deducible. Es decir, no será posible llamar a la función foo a continuación sin especificar explícitamente los argumentos de la plantilla.

template <typename T> void foo (Vec<T>::type&);

Entonces, la sintaxis es algo fea. Preferiríamos evitar los anidados ::type . Preferiríamos algo como lo siguiente:

template <typename T>
using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below
Vec<int> p; // sample usage

Tenga en cuenta que evitamos específicamente el término “plantilla typedef” e introducimos la nueva sintaxis que involucra el par “using” y “=” para ayudar a evitar confusiones: no estamos definiendo ningún tipo aquí, estamos introduciendo un sinónimo (es decir, alias) para Una abstracción de una identificación de tipo (es decir, expresión de tipo) que involucra parámetros de plantilla. Si los parámetros de la plantilla se usan en contextos deducibles en la expresión de tipo, cada vez que se use el alias de la plantilla para formar un id de plantilla, se pueden deducir los valores de los parámetros de la plantilla correspondiente; más adelante se hará más sobre esto. En cualquier caso, ahora es posible escribir funciones genéricas que operan Vec<T>en un contexto deducible, y la sintaxis también se mejora. Por ejemplo, podríamos reescribir foo como:

template <typename T> void foo (Vec<T>&);

Subrayamos aquí que una de las razones principales para proponer alias de plantilla fue para que la deducción de argumentos y el llamado a foo(p) tener éxito.


El documento de seguimiento n1489 explica por qué en usinglugar de usar typedef:

Se ha sugerido (re) utilizar la palabra clave typedef, como se hizo en el documento [4], para introducir alias de plantilla:

template<class T> 
    typedef std::vector<T, MyAllocator<T> > Vec;

Esa notación tiene la ventaja de usar una palabra clave ya conocida para introducir un alias de tipo. Sin embargo, también muestra varias desventajas, entre las cuales se encuentra la confusión de usar una palabra clave conocida para introducir un alias para un nombre de tipo en un contexto donde el alias no designa un tipo, sino una plantilla; Vecno es un alias para un tipo, y no debe tomarse para un typedef-name. El nombre Veces un nombre para la familia std::vector< [bullet] , MyAllocator< [bullet] > > , donde la viñeta es un marcador de posición para un nombre de tipo. En consecuencia, no proponemos la sintaxis "typedef". Por otro lado la oración

template<class T>
    using Vec = std::vector<T, MyAllocator<T> >;

se puede leer / interpretar como: a partir de ahora, lo usaré Vec<T>como sinónimo de std::vector<T, MyAllocator<T> >. Con esa lectura, la nueva sintaxis para el alias parece razonablemente lógica.

Creo que la distinción importante se hace aquí, alias es en lugar de tipo s. Otra cita del mismo documento:

Una declaración de alias es una declaración, y no una definición. Una declaración de alias introduce un nombre en una región declarativa como un alias para el tipo designado por el lado derecho de la declaración. El núcleo de esta propuesta se refiere a los alias de nombre de tipo, pero la notación obviamente puede generalizarse para proporcionar ortografías alternativas de alias de espacio de nombres o conjunto de nombres de funciones sobrecargadas (consulte ✁ 2.3 para una discusión más detallada). [ Mi nota: esa sección discute cómo puede ser esa sintaxis y las razones por las cuales no es parte de la propuesta. ] Cabe señalar que la declaración de alias de producción gramatical es aceptable en cualquier lugar donde sea aceptable una declaración typedef o una definición de alias de espacio de nombres.

Resumen, para el papel de using:

  • alias de plantilla (o tipos de plantilla, se prefiere el primero por su nombre)
  • alias de espacio de nombres (es decir, namespace PO = boost::program_optionsy using PO = ...equivalente)
  • dice el documento A typedef declaration can be viewed as a special case of non-template alias-declaration. Es un cambio estético, y se considera idéntico en este caso.
  • traer algo al alcance (por ejemplo, namespace stdal alcance global), funciones miembro, heredar constructores

No se puede usar para:

int i;
using r = i; // compile-error

En cambio, haga:

using r = decltype(i);

Nombrar un conjunto de sobrecargas.

// bring cos into scope
using std::cos;

// invalid syntax
using std::cos(double);

// not allowed, instead use Bjarne Stroustrup function pointer alias example
using test = std::cos(double);
Gabriel Staples
fuente
2
@ user3111311 ¿Qué otra palabra clave tenía en mente? "auto"? "Registrarse"?
Raymond Chen
2
using P = [](double)->void;es decir, AFAIK, no es válido C ++ 11. Sin embargo, esto es: using P = auto(double)->void;y produce un tipo de función (como P*un puntero de función).
dyp
2
Su nombre es Bjarne Stroustrup;) (tenga en cuenta la segunda r en Stroustrup)
dyp
1
@RaymondChen: en realidad registerno sonaría tan mal, está en:register X as Y
MFH
1
Desafortunadamente, registercomienza una declaración variable, por lo que esto ya tiene un significado. Declarar una variable de registro llamada Y de tipo X.
Raymond Chen