¿Cuál es la diferencia entre 'typedef' y 'using' en C ++ 11?

905

Sé que en C ++ 11 ahora podemos usar usingpara escribir alias de tipo, como typedefs:

typedef int MyInt;

Es, por lo que entiendo, equivalente a:

using MyInt = int;

Y esa nueva sintaxis surgió del esfuerzo por tener una forma de expresar " template typedef":

template< class T > using MyType = AnotherType< T, MyAllocatorType >;

Pero, con los dos primeros ejemplos que no son de plantilla, ¿hay otras diferencias sutiles en el estándar? Por ejemplo, typedefs aliasing de una manera "débil". Es decir, no crea un nuevo tipo, sino solo un nuevo nombre (las conversiones están implícitas entre esos nombres).

¿Es lo mismo con usingo genera un nuevo tipo? ¿Hay alguna diferencia?

Klaim
fuente
215
Personalmente, prefiero la nueva sintaxis porque es mucho más similar a la asignación de variables regular, lo que mejora la legibilidad. Por ejemplo, ¿prefieres typedef void (&MyFunc)(int,int);o using MyFunc = void(int,int);?
Matthieu M.
13
Estoy totalmente de acuerdo, solo uso la nueva sintaxis ahora. Por eso estaba preguntando, para asegurarme de que realmente no hay diferencia.
Klaim
80
@MatthieuM. esos dos son diferentes por cierto. Debería ser typedef void MyFunc(int,int);(que en realidad no se ve tan mal), ousing MyFunc = void(&)(int,int);
R. Martinho Fernandes
55
@ R.MartinhoFernandes ¿por qué necesitas (&) adentro using MyFunc = void(&)(int,int);? MyFuncQué significa es una referencia a una función? ¿Qué pasa si omite el & ?
Rico
77
Sí, es una referencia de función. Es equivalente a typedef void (&MyFunc)(int,int);. Si omite el &es equivalente atypedef void MyFunc(int,int);
R. Martinho Fernandes

Respuestas:

585

Son equivalentes, del estándar (énfasis mío) (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.

Jesse Good
fuente
24
De la respuesta, la usingpalabra clave parece ser un superconjunto de typedef. Entonces, será typdefobsoleto en el futuro?
iammilind
49
La desaprobación no necesariamente indica intención de eliminar: simplemente es una recomendación muy fuerte para preferir otros medios.
Iron Savior
18
Pero luego me pregunto por qué no solo permitieron que typedef fuera tentado. Recuerdo haber leído en alguna parte que introdujeron la usingsintaxis precisamente porque la typedefsemántica no funcionaba bien con las plantillas. Lo que de alguna manera contradice el hecho de que usingse define para tener exactamente la misma semántica.
celtschk
12
@celtschk: El motivo se menciona en la propuesta n1489. Un alias de plantilla no es un alias para un tipo, sino un alias para un grupo de plantillas. Hacer una distinción entre typedefel sentido de la necesidad de una nueva sintaxis. Además, tenga en cuenta que la pregunta del OP es acerca de la diferencia entre las versiones sin plantilla.
Jesse Good
44
Entonces, ¿por qué se introdujo esta redundancia? 2 sintaxis para el mismo propósito. Y no veo typdefser desaprobado nunca.
Benoît
237

Son en gran medida iguales, excepto que:

La declaración de alias es compatible con las plantillas, mientras que el estilo C typedef no lo es.

Zhongming Qu
fuente
29
Particularmente aficionado a la simplicidad de la respuesta y señalar el origen de la composición tipográfica.
g24l
1
@ g24l te refieres a typedef ... probablemente
Marc.2377
¿Cuál es la diferencia entre C y C ++ en typedefsi puedo preguntar?
McSinyx
196

El uso de la sintaxis tiene una ventaja cuando se usa dentro de plantillas. Si necesita la abstracción de tipo, pero también necesita mantener el parámetro de plantilla para poder especificarlo en el futuro. Deberías escribir algo como esto.

template <typename T> struct whatever {};

template <typename T> struct rebind
{
  typedef whatever<T> type; // to make it possible to substitue the whatever in future.
};

rebind<int>::type variable;

template <typename U> struct bar { typename rebind<U>::type _var_member; }

Pero el uso de la sintaxis simplifica este caso de uso.

template <typename T> using my_type = whatever<T>;

my_type<int> variable;
template <typename U> struct baz { my_type<U> _var_member; }
4xy
fuente
35
Sin embargo, ya señalé esto en la pregunta. Mi pregunta es sobre si no usa la plantilla, ¿hay alguna diferencia con typedef? Como, por ejemplo, cuando usa 'Foo foo {init_value};' en lugar de 'Foo foo (init_value)' se supone que ambos deben hacer lo mismo pero no siguen exactamente las mismas reglas. Entonces, me preguntaba si había una diferencia oculta similar con el uso de / typedef.
Klaim
24

Son esencialmente lo mismo, pero usingproporciona lo alias templatesque es bastante útil. Un buen ejemplo que pude encontrar es el siguiente:

namespace std {
 template<typename T> using add_const_t = typename add_const<T>::type;
}

Entonces, podemos usar en std::add_const_t<T>lugar detypename std::add_const<T>::type

Validus Oculus
fuente
Hasta donde sé, es un comportamiento indefinido agregar algo dentro del espacio de nombres
estándar
55
@someonewithpc No estaba agregando nada, ya existe, solo estaba mostrando un ejemplo de uso de nombre de tipo. Consulte en.cppreference.com/w/cpp/types/add_cv
Validus Oculus
9

Sé que el póster original tiene una gran respuesta, pero para cualquiera que se encuentre con este hilo como yo, hay una nota importante de la propuesta que creo que agrega algo de valor a la discusión aquí, particularmente a las preocupaciones en los comentarios sobre si la typedefpalabra clave es se marcará como obsoleto en el futuro o se eliminará por ser redundante / antiguo:

Se ha sugerido (re) usar la palabra clave typedef ... 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 [sic] entre las cuales 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; noVec es un alias para un tipo, y no debe tomarse como typedef-name. El nombre es un nombre para la familia , donde la viñeta es un marcador de posición para un nombre de tipo. Por consiguiente, no proponemos la sintaxis "typedef". Por otro lado, la oraciónVecstd::vector<•, MyAllocator<•> >

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 destd::vector<T, MyAllocator<T> > . Con esa lectura, la nueva sintaxis para el alias parece razonablemente lógica.

Para mí, esto implica un soporte continuo para la typedefpalabra clave en C ++ porque aún puede hacer que el código sea más legible y comprensible .

La actualización de la usingpalabra clave fue específicamente para plantillas, y (como se señaló en la respuesta aceptada) cuando se trabaja con no plantillas usingy typedefson mecánicamente idénticas, por lo que la elección depende totalmente del programador por motivos de legibilidad y comunicación de intenciones. .

Bosque robótico
fuente
2

Ambas palabras clave son equivalentes, pero hay algunas advertencias. Una es que declarar un puntero de función con using T = int (*)(int, int);es más claro que con typedef int (*T)(int, int);. En segundo lugar, esa forma de alias de plantilla no es posible con typedef. En tercer lugar, exponer C API requeriría typedefen encabezados públicos.

marski
fuente
0

Las declaraciones de typedef pueden, mientras que las declaraciones de alias no pueden, usarse como declaraciones de inicialización

Pero, con los dos primeros ejemplos que no son plantillas, ¿hay alguna otra diferencia sutil en el estándar?

Aunque sea un caso de esquina, una declaración typedef es una instrucción init y, por lo tanto, se puede usar en contextos que permiten instrucciones de inicialización

// C++11 (C++03) (init. statement in for loop iteration statements).
for(typedef int Foo; Foo{} != 0;) {}

// C++17 (if and switch initialization statements).
if (typedef int Foo; true) { (void)Foo{}; }
//  ^^^^^^^^^^^^^^^ init-statement

switch(typedef int Foo; 0) { case 0: (void)Foo{}; }
//     ^^^^^^^^^^^^^^^ init-statement

// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for(typedef int Foo; Foo f : v) { (void)f; }
//  ^^^^^^^^^^^^^^^ init-statement

mientras que una declaración de alias no es una instrucción init y , por lo tanto, no puede usarse en contextos que permitan instrucciones de inicialización

// C++ 11.
for(using Foo = int; Foo{} != 0;) {}
//  ^^^^^^^^^^^^^^^ error: expected expression

// C++17 (initialization expressions in switch and if statements).
if (using Foo = int; true) { (void)Foo{}; }
//  ^^^^^^^^^^^^^^^ error: expected expression

switch(using Foo = int; 0) { case 0: (void)Foo{}; }
//     ^^^^^^^^^^^^^^^ error: expected expression

// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for(using Foo = int; Foo f : v) { (void)f; }
//  ^^^^^^^^^^^^^^^ error: expected expression
dfri
fuente