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

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

typedef int MyInt;

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

using MyInt = int;

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

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

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

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

8 ответов

Решение

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

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

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

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

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

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

Все приведенные ниже стандартные ссылки относятся к N4659: рабочий проект, выпущенный после Kona за март 2017 г. / DIS C++17.


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

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

  • Различия в семантике: нет.
  • Различия в допустимых контекстах: некоторые(1).

(1) В дополнение к примерам шаблонов псевдонимов, которые уже упоминались в исходном посте.

Та же семантика

В соответствии с [dcl.typedef] / 2 [извлечение, выделено мной ]

[dcl.typedef] / 2 Имя typedef также может быть введено с помощью объявления псевдонима. Идентификатор следующего заusingключевое слово становитсяtypedef-name, а необязательный атрибут-спецификатор-seq, следующий за идентификатором, принадлежит этому typedef-name. Такоеимя typedef имеет ту же семантику, как если бы оно было введеноtypedefспецификатор.[...]

имя ЬурейеЕ вводится с помощью псевдонима декларирование имеет ту же семантику, как если бы были введеныtypedef декларация.

Тонкая разница в разрешенных контекстах

Однако это не означает, что эти два варианта имеют одинаковые ограничения относительно того, в каком контексте они могут использоваться. И действительно, хотя и в крайнем случае, объявление typedef является оператором инициализации и, таким образом, может использоваться в контекстах, допускающих операторы инициализации.

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

Они по сути одинаковы, но 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

Я знаю, что у оригинального плаката есть отличный ответ, но для любого, кто наткнется на эту ветку, как у меня, есть важное замечание из предложения, которое, я думаю, добавляет что-то ценное к обсуждению здесь, особенно к опасениям в комментариях по поводу того,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 механически идентичны, поэтому выбор полностью зависит от программиста на основании удобочитаемости и передачи намерений.

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

На данный момент С++ 23 станет ближе друг к другу: P2360 предлагает составить оператор инициализации, такой как те, которые перечислены в user4573247 как error: expected expression.

Однако даже с P2360 a не может быть шаблоном.

В целом, usingстрого мощнее, чем typedef, и ИМО более читаемый тоже.

Другие вопросы по тегам