В чем разница между typedef и using в C++11?
Я знаю, что в C++11 теперь мы можем использовать using
написать псевдоним типа, как typedef
s:
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
, и ИМО более читаемый тоже.