В чем разница между typedef и using в C ++ 11?

905

Я знаю, что в C ++ 11 теперь мы можем использовать usingдля записи псевдоним типа, например, typedefs:

typedef int MyInt;

Насколько я понимаю, эквивалентно:

using MyInt = int;

И этот новый синтаксис возник в результате попытки выразить " template typedef":

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

Но с первыми двумя примерами, не относящимися к шаблону, есть ли еще какие-то тонкие различия в стандарте? Например, typedefs делают псевдонимы «слабым» способом. То есть он не создает новый тип, а только новое имя (между этими именами неявные преобразования).

Это то же самое с usingили это генерирует новый тип? Есть ли различия?

Klaim
источник
215
Я лично предпочитаю новый синтаксис, потому что он намного больше похож на обычное назначение переменных, улучшая читабельность. Например, вы предпочитаете typedef void (&MyFunc)(int,int);или using MyFunc = void(int,int);?
Матье М.
13
Я полностью согласен, я использую только новый синтаксис сейчас. Вот почему я спрашивал, чтобы убедиться, что на самом деле нет никакой разницы.
Klaim
80
@MatthieuM. эти два разные, кстати. Это должно быть typedef void MyFunc(int,int);(что на самом деле выглядит не так уж плохо), илиusing MyFunc = void(&)(int,int);
Р. Мартиньо Фернандес
5
@ R.MartinhoFernandes зачем вам это нужно (и) в using MyFunc = void(&)(int,int);? значит ли MyFuncэто ссылка на функцию? Что делать, если вы пропустите & ?
Богатый
7
Да, это ссылка на функцию. Это эквивалентно typedef void (&MyFunc)(int,int);. Если вы пропустите &это эквивалентноtypedef void MyFunc(int,int);
Р. Мартиньо Фернандес

Ответы:

585

Они эквивалентны стандарту (выделено мной) (7.1.3.2):

Typedef-name также может быть введено объявлением псевдонима. Идентификатор, следующий за ключевым словом using, становится typedef-name, а необязательный атрибут-identifier-seq, следующий за идентификатором, относится к этому typedef-name. Он имеет такую ​​же семантику, как если бы он был введен спецификатором typedef. В частности, он не определяет новый тип и не должен появляться в идентификаторе типа.

Джесси Гуд
источник
24
Из ответа, usingключевое слово, кажется, надмножество typedef. Тогда будет typdefосуждается в будущем?
iammilind
49
Устаревание не обязательно указывает на намерение удалить - это просто очень сильная рекомендация отдавать предпочтение другим средствам.
Железный Спас
18
Но потом я удивляюсь, почему они не позволили шаблонизировать typedef. Я помню, что где-то читал, что они ввели usingсинтаксис именно потому, что typedefсемантика плохо работала с шаблонами. Что как-то противоречит тому факту, что usingон имеет точно такую ​​же семантику.
celtschk
12
@celtschk: О причине говорится в предложении n1489. Псевдоним шаблона - это не псевдоним для типа, а псевдоним для группы шаблонов. Чтобы сделать различие между typedefощущаемой необходимостью нового синтаксиса. Кроме того, имейте в виду, что вопрос OP о разнице между не шаблонными версиями.
Джесси Гуд
4
Так почему же появилась эта избыточность? 2 синтаксиса для той же цели. И я не вижу, typdefчтобы меня осуждали никогда.
Бенуа
237

Они в основном одинаковы, за исключением того, что:

Объявление псевдонима совместимо с шаблонами, в то время как typedef в стиле C - нет.

Чжунмин Цюй
источник
29
Особенно любит простоту ответа и указание на происхождение шрифта.
g24l
1
@ g24l ты имеешь в виду typedef ... вероятно
23
В чем разница между C и C ++, typedefесли я могу спросить?
McSinyx
196

С использованием синтаксиса имеет преимущество при использовании в шаблонах. Если вам нужна абстракция типа, но также необходимо сохранить параметр шаблона, чтобы его можно было указывать в будущем. Вы должны написать что-то вроде этого.

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

Но использование синтаксиса упрощает этот вариант использования.

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

my_type<int> variable;
template <typename U> struct baz { my_type<U> _var_member; }
4xy
источник
35
Я уже указал это в вопросе, хотя. Мой вопрос о том, если вы не используете шаблон, есть ли разница с typedef. Как, например, когда вы используете 'Foo foo {init_value};' вместо 'Foo foo (init_value)' оба должны делать одно и то же, но не следуют одним и тем же правилам. Поэтому мне было интересно, была ли похожая скрытая разница с использованием / typedef.
Klaim
24

Они по сути одинаковы, но usingпредоставляют, alias templatesчто весьма полезно. Один хороший пример, который я мог найти, заключается в следующем:

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

Таким образом, мы можем использовать std::add_const_t<T>вместоtypename std::add_const<T>::type

Валидус Окулус
источник
Насколько я знаю, это неопределенное поведение - добавлять что-либо в пространство имен std
someonewithpc
5
@someonewithpc Я ничего не добавлял, он уже существует, я просто демонстрировал пример использования typename. Пожалуйста, проверьте en.cppreference.com/w/cpp/types/add_cv
Oculus
9

Я знаю, что у оригинального плаката есть отличный ответ, но для любого, кто спотыкается в этой теме, как у меня, есть важное примечание из предложения, которое, я думаю, добавляет что-то ценное к обсуждению здесь, особенно к проблемам в комментариях о том, typedefявляется ли ключевое слово будет помечен как устаревший в будущем или удален за то, что он является избыточным / старым:

Было предложено (повторно) использовать ключевое слово typedef ..., чтобы ввести псевдонимы шаблона:

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

Преимущество этой нотации заключается в использовании уже известного ключевого слова для введения псевдонима типа. Однако он также отображает несколько недостатков [sic], среди которых путаница в использовании ключевого слова, известного для введения псевдонима для имени типа в контексте, где псевдоним не обозначает тип, а шаблон; Vecэто не псевдоним типа, и не должны быть приняты для имени-ЬурейеГо. Имя Vec- это имя для семейства, std::vector<•, MyAllocator<•> >где пуля является заполнителем для имени типа. Следовательно, мы не предлагаем синтаксис «typedef». С другой стороны, предложение

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

можно читать / интерпретировать как: теперь я буду использовать Vec<T>как синоним дляstd::vector<T, MyAllocator<T> > . С этим чтением новый синтаксис для псевдонимов кажется достаточно логичным.

Для меня это подразумевает постоянную поддержку typedefключевого слова в C ++, потому что он все еще может сделать код более читабельным и понятным .

Обновление usingключевого слова было специально для шаблонов и (как было указано в принятом ответе), когда вы работаете с не-шаблонами usingи typedefмеханически идентичны, так что выбор полностью за программистом на основе читабельности и передачи намерений ,

RoboticForest
источник
2

Оба ключевых слова эквивалентны, но есть несколько предостережений. Одна из них заключается в том, что объявление указателя функции с помощью using T = int (*)(int, int);более четкого, чем с typedef int (*T)(int, int);. Во-вторых, шаблон псевдонима формы невозможен typedef. В-третьих, раскрытие C API потребует typedefпубличных заголовков.

Marski
источник
0

Объявления Typedef могут, тогда как объявления псевдонимов не могут быть использованы в качестве операторов инициализации

Но с первыми двумя примерами, не относящимися к шаблону, есть ли еще какие-то тонкие различия в стандарте?

Хотя и в угловом случае, объявление typedef является оператором init и, таким образом, может использоваться в контекстах, которые допускают операторы инициализации

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

в то время как псевдоним декларация является не INIT-заявление , и не может , таким образом , можно использовать в условиях , которые позволяют операторы инициализации

// 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
источник