Qual é a diferença entre 'typedef' e 'using' no C ++ 11?

905

Eu sei que no C ++ 11 agora podemos usar usingpara escrever alias de tipo, como typedefs:

typedef int MyInt;

É, pelo que entendi, equivalente a:

using MyInt = int;

E essa nova sintaxe surgiu do esforço de ter uma maneira de expressar " template typedef":

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

Mas, com os dois primeiros exemplos não-modelo, existem outras diferenças sutis no padrão? Por exemplo, typedefs faz alias de maneira "fraca". Ou seja, ele não cria um novo tipo, mas apenas um novo nome (as conversões estão implícitas entre esses nomes).

É o mesmo com usingou gera um novo tipo? Existem diferenças?

Klaim
fonte
215
Pessoalmente, prefiro a nova sintaxe, porque é muito mais semelhante à atribuição regular de variáveis, melhorando a legibilidade. Por exemplo, você prefere typedef void (&MyFunc)(int,int);ou using MyFunc = void(int,int);?
Matthieu M.
13
Concordo plenamente, só uso a nova sintaxe agora. Por isso estava perguntando, para ter certeza de que realmente não há diferença.
Klaim
80
@MatthieuM. esses dois são diferentes. Deve ser typedef void MyFunc(int,int);(que na verdade não parece tão ruim), ouusing MyFunc = void(&)(int,int);
R. Martinho Fernandes
5
@ R.MartinhoFernandes Por que você precisa (e) em using MyFunc = void(&)(int,int);? significa que MyFuncé uma referência a uma função? e se você omitir o & ?
18715 Rich
7
Sim, é uma referência de função. É equivalente a typedef void (&MyFunc)(int,int);. Se você omitir o &equivalente atypedef void MyFunc(int,int);
R. Martinho Fernandes

Respostas:

585

Eles são equivalentes, do padrão (ênfase minha) (7.1.3.2):

Um typedef-name também pode ser introduzido por uma declaração de alias. O identificador que segue a palavra-chave using se torna um typedef-name e o atributo-specifier-seq opcional após o identificador pertence a esse typedef-name. Tem a mesma semântica como se tivesse sido introduzida pelo especificador typedef. Em particular, ele não define um novo tipo e não deve aparecer no ID do tipo.

Jesse Good
fonte
24
A partir da resposta, a usingpalavra-chave parece ser um superconjunto de typedef. Então, será typdefpreterido no futuro?
Iammilind
49
A depreciação não indica necessariamente a intenção de remover - ela é apenas uma recomendação muito forte de preferir outros meios.
Iron Saviil
18
Mas então eu me pergunto por que eles não apenas permitiram que o typedef fosse modelado. Lembro-me de ter lido em algum lugar que eles introduziram a usingsintaxe precisamente porque a typedefsemântica não funcionou bem com modelos. O que de alguma forma contradiz o fato de que usingé definido como tendo exatamente a mesma semântica.
Celtschk
12
@celtschk: O motivo é mencionado na proposta n1489. Um alias de modelo não é um alias para um tipo, mas um alias para um grupo de modelos. Fazer uma distinção entre typedefa necessidade de uma nova sintaxe. Além disso, lembre-se de que a pergunta do OP é sobre a diferença entre versões que não são modelos.
Jesse Good
4
Então, por que essa redundância foi introduzida? 2 sintaxes para a mesma finalidade. E nunca vejo typdefser preterido.
Benoît
237

Eles são basicamente os mesmos, exceto que:

A declaração de alias é compatível com modelos, enquanto o estilo C typedef não é.

Zhongming Qu
fonte
29
Particularmente apaixonado pela simplicidade da resposta e apontando a origem da composição.
G24l
1
@ g24l você quer dizer typedef ... provavelmente
Marc.2377
Qual é a diferença entre C e C ++ em typedefse eu perguntar?
McSinyx 16/12/19
196

A sintaxe de uso tem uma vantagem quando usada em modelos. Se você precisar do tipo abstração, mas também precisar manter o parâmetro do modelo para poder ser especificado no futuro. Você deve escrever algo assim.

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; }

Mas o uso da sintaxe simplifica esse 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
fonte
35
Eu já apontei isso na pergunta. Minha pergunta é sobre se você não usar o modelo, existe alguma diferença com o typedef. Como, por exemplo, quando você usa 'Foo foo {init_value};' em vez de 'Foo foo (init_value)', ambos devem fazer a mesma coisa, mas não seguem exatamente as mesmas regras. Então, eu queria saber se havia uma diferença oculta semelhante com o uso de / typedef.
Klaim
24

Eles são essencialmente os mesmos, mas usingfornece o alias templatesque é bastante útil. Um bom exemplo que pude encontrar é o seguinte:

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

Então, podemos usar em std::add_const_t<T>vez detypename std::add_const<T>::type

Validus Oculus
fonte
Tanto quanto eu sei, é um comportamento indefinido para adicionar qualquer coisa dentro do namespace std
someonewithpc
5
@someonewithpc Eu não estava adicionando nada, ele já existe, estava apenas mostrando um exemplo de uso do nome de tipo. Por favor, verifique en.cppreference.com/w/cpp/types/add_cv
Validus Oculus
9

Sei que o pôster original tem uma ótima resposta, mas para qualquer um que tropeçar nesse tópico como eu, há uma nota importante da proposta que, em minha opinião, agrega algo de valor à discussão aqui, principalmente às preocupações nos comentários sobre se a typedefpalavra-chave é será marcado como obsoleto no futuro ou removido por ser redundante / antigo:

Foi sugerido (re) usar a palavra-chave typedef ... para introduzir aliases de modelo:

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

Essa notação tem a vantagem de usar uma palavra-chave já conhecida por introduzir um alias de tipo. No entanto, ele também exibe várias desvantagens [sic], entre as quais a confusão de usar uma palavra-chave conhecida por introduzir um alias para um nome de tipo em um contexto em que o alias não designa um tipo, mas um modelo; nãoVec é um alias para um tipo e não deve ser usado como um typedef-name. O nome é um nome para a família - onde o marcador é um espaço reservado para um nome de tipo. Posteriormente, não propomos a sintaxe “typedef”. Por outro lado, a fraseVecstd::vector<•, MyAllocator<•> >

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

pode ser lido / interpretado como: a partir de agora, usarei Vec<T>como sinônimostd::vector<T, MyAllocator<T> > . Com essa leitura, a nova sintaxe para alias parece razoavelmente lógica.

Para mim, isso implica suporte contínuo para a typedefpalavra - chave em C ++, pois ainda pode tornar o código mais legível e compreensível .

A atualização da usingpalavra - chave foi especificamente para modelos e (como indicado na resposta aceita) quando você trabalha com não modelos usinge typedefé mecanicamente idêntico, portanto, a escolha depende totalmente do programador com base na legibilidade e na comunicação da intenção. .

RoboticForest
fonte
2

Ambas as palavras-chave são equivalentes, mas existem algumas ressalvas. Uma é que declarar um ponteiro de função com using T = int (*)(int, int);é mais claro do que com typedef int (*T)(int, int);. O segundo é que o formulário de alias de modelo não é possível com typedef. Terceiro, é necessário expor a API C typedefem cabeçalhos públicos.

Marski
fonte
0

As declarações Typedef podem, enquanto as declarações de alias não, podem ser usadas como instruções de inicialização

Mas, com os dois primeiros exemplos não-modelo, existem outras diferenças sutis no padrão?

Embora seja um caso de canto, uma declaração typedef é uma instrução init e, portanto, pode ser usada em contextos que permitem instruções de inicialização

// 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

Considerando que uma declaração de alias não é uma instrução init e , portanto, não pode ser usada em contextos que permitem instruções de inicialização

// 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
fonte