Wat is het verschil tussen ‘typedef’ en ‘using’ in C++11?

Ik weet dat we in C++11 nu usingkunnen gebruiken om type-alias te schrijven, zoals typedefs:

typedef int MyInt;

Is, voor zover ik het begrijp, gelijk aan:

using MyInt = int;

En die nieuwe syntaxis kwam voort uit de poging om een ​​manier te hebben om “template typedef” uit te drukken:

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

Maar, met de eerste twee niet-sjabloonvoorbeelden, zijn er nog andere subtiele verschillen in de standaard? Bijvoorbeeld, typedefs doen aliasing op een “zwakke” manier. Dat wil zeggen dat er geen nieuw type wordt gemaakt, maar alleen een nieuwe naam (conversies zijn impliciet tussen die namen).

Is het hetzelfde met usingof genereert het een nieuw type? Zijn er verschillen?


Antwoord 1, autoriteit 100%

Ze zijn equivalent, van de standaard (nadruk van mij) (7.1.3.2):

Een typedef-naam kan ook worden geïntroduceerd door een alias-declaratie. De
identifier na het gebruik van trefwoord wordt een typedef-naam en de
optioneel attribuut-specificatie-seq na de identifier appertains
naar dat typedef-naam. Het heeft dezelfde semantiek als ware het
geïntroduceerd door de typedef-specificatie.
In het bijzonder is het
definieert geen nieuw type en het zal niet voorkomen in de type-id.


Antwoord 2, autoriteit 48%

Ze zijn grotendeels hetzelfde, behalve dat:

De aliasdeclaratie is compatibel met sjablonen, terwijl de C
stijltypedef niet.


Antwoord 3, autoriteit 34%

De syntaxis gebruikenheeft een voordeel bij gebruik binnen sjablonen. Als u het type abstractie nodig hebt, maar ook de sjabloonparameter wilt behouden, zodat deze in de toekomst kan worden opgegeven. Je zou zoiets als dit moeten schrijven.

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

Maar het gebruik vansyntaxis vereenvoudigt deze use case.

template <typename T> using my_type = whatever<T>;
my_type<int> variable;
template <typename U> struct baz { my_type<U> _var_member; }

Antwoord 4, autoriteit 18%

Alle standaardreferenties hieronder verwijzen naar N4659: maart 2017 post- Kona werkend concept/C++17 DIS.


Typedef-declaraties kunnen, terwijl aliasdeclaraties niet(+)kunnen worden gebruikt als initialisatie-instructies

Maar met de eerste twee niet-sjabloonvoorbeelden zijn
zijn er nog andere subtiele verschillen in de standaard?

  • Verschillen in semantiek: geen.
  • Verschillen in toegestane contexten: sommige(++).

(+) P2360R0 (Init-statement uitbreiden om alias-declaration toe te staan) is goedgekeurd door CWGen vanaf C++23 is deze inconsistentie tussen typedef-declaraties en alias-declaraties verwijderd.

(++) Naast de voorbeelden van alias-sjablonen, die al in het oorspronkelijke bericht zijn genoemd.

Dezelfde semantiek

Zoals geregeld door [dcl.typedef]/2[uittreksel, nadrukvan mij]

[dcl.typedef]/2A
typedef-name
kan ook worden ingeleid door een
alias-declaration.
De identifierdie volgt op het usingtrefwoord wordt a
typedef-nameen de optionele attribute-specifier-seqna de identifierhoort bij die typedef-name. Zo’n
typedef-nameheeft dezelfde semantiek alsof het werd geïntroduceerd door de typedef-specificatie.
[…]

een typedef-namegeïntroduceerd door een alias-declarationheeft dezelfde semantiekalsof het werd geïntroduceerd door de typedefaangifte.

Subtiel verschil in toegestane contexten

Dit betekent echter nietdat de twee varianten dezelfde beperkingen hebben met betrekking tot de contextenwaarin ze kunnen worden gebruikt. En inderdaad, zij het een hoekzaak, een typedef-declaratieis een init-statementen kan dus worden gebruikt in contexten die initialisatie-instructies toestaan

// C++11 (C++03) (init. statement in for loop iteration statements).
for (typedef int Foo; Foo{} != 0;)
//   ^^^^^^^^^^^^^^^ init-statement
{
}
// C++17 (if and switch initialization statements).
if (typedef int Foo; true)
//  ^^^^^^^^^^^^^^^ init-statement
{
    (void)Foo{};
}
switch (typedef int Foo; 0)
//      ^^^^^^^^^^^^^^^ init-statement
{
    case 0: (void)Foo{};
}
// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for (typedef int Foo; Foo f : v)
//   ^^^^^^^^^^^^^^^ init-statement
{
    (void)f;
}
for (typedef struct { int x; int y;} P; auto [x, y] : {P{1, 1}, {1, 2}, {3, 5}})
//   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ init-statement
{
    (void)x;
    (void)y;
}

terwijl een alias-declarationis geeneen init-statement, en daarom mag nietworden gebruikt in contexten waarin initialisatie-statements zijn toegestaan

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

Antwoord 5, autoriteit 4%

Ze zijn in wezen hetzelfde, maar usinglevert alias templatesop, wat best handig is. Een goed voorbeeld dat ik kon vinden is het volgende:

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

We kunnen dus std::add_const_t<T>gebruiken in plaats van typename std::add_const<T>::type


Antwoord 6, autoriteit 3%

Ik weet dat de originele poster een geweldig antwoord heeft, maar voor iedereen die op deze draad struikelt zoals ik heb, is er een belangrijke opmerking van het voorstelwaarvan ik denk dat het iets van waarde toevoegt aan de discussie hier, met name aan de bezorgdheid in de opmerkingen over of de typedefzoekwoord wordt in de toekomst gemarkeerd als verouderd of verwijderd omdat het overbodig/oud is:

Er is gesuggereerd om het trefwoord typedef … te (her)gebruiken om sjabloonaliassen te introduceren:

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

Die notatie heeft het voordeel dat een reeds bekend trefwoord wordt gebruikt om een ​​type-alias te introduceren. Het vertoont echter ook verschillende nadelen [sic] waaronder de verwarring van het gebruik van een trefwoord waarvan bekend is dat het een alias introduceert voor een typenaam in een context waarin de alias geen type aanduidt, maar een sjabloon; Vecis geeneen alias voor een type en mag niet worden gebruikt voor een typedef-naam. De naam Vecis een naam voor de familie std::vector<•, MyAllocator<•> >– waarbij het opsommingsteken een tijdelijke aanduiding is voor een typenaam. Daarom stellen we de syntaxis “typedef” niet voor. Aan de andere kant is de zin

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

kan worden gelezen/geinterpreteerd als: vanaf nu gebruik ik Vec<T>als synoniem voor std::vector<T, MyAllocator<T> >. Met die lezing lijkt de nieuwe syntaxis voor aliasing redelijk logisch.

Voor mij betekent dit voortdurende ondersteuning voor het trefwoord typedefin C++, omdat het code nog steeds leesbaarder en begrijpelijkerkan maken.

Het bijwerken van het trefwoord usingwas specifiek voor sjablonen en (zoals aangegeven in het geaccepteerde antwoord) wanneer u werkt met niet-sjablonen usingen typedefzijn mechanisch identiek, dus de keuze is geheel aan de programmeur op grond van leesbaarheid en communicatie van intentie.


Antwoord 7, autoriteit 2%

Beide zoekwoorden zijn equivalent, maar er zijn enkele kanttekeningen. Een daarvan is dat het declareren van een functieaanwijzer met using T = int (*)(int, int);duidelijker is dan met typedef int (*T)(int, int);. Ten tweede is de vorm van een sjabloonalias niet mogelijk met typedef. Ten derde is het blootleggen van de C API vereist typedefin openbare headers.


Antwoord 8

Vanaf nu zal C++23 typedefen usingdichter bij elkaar brengen: P2360stelt voor dat usingeen init-statementvormt zoals die vermeld in @dfrib‘s antwoord als error: expected expression.

Maar zelfs met P2360 kan een typedefgeen sjabloon zijn.

In totaal is usingstrikt krachtiger dan typedef, en IMO ook leesbaarder.

Other episodes