Why Do Type Aliases in C++ Use 'Using' Instead of 'Typedef' in Their Syntax

Why do type aliases in C++ use 'using' instead of 'typedef' in their syntax?

What you suggest was actually proposed back in 2002 in document N1406 by Herb Sutter. It would allow, for example, to write:

template<typename T> typedef X<T,int> Xi; 

This was later revised in N1449 by Gabriel Dos Reis and Mat Marcus. They adopt the using syntax, and note the following:

Note that we specifically avoid the term “typedef template” and introduce the new syntax involving the pair “using” and “=” to help avoid confusion: we are not defining any types here, we are introducing a synonym (i.e. alias) for an abstraction of a type-id (i.e. type expression) involving template parameters.

They also state:

Two straw polls were taken regarding syntax. A strong majority voted to avoid the typedef template syntax, in favor of the “=” syntax. A second vote indicated strong preference for the “using” keyword as opposed to a word like “alias” or the absence of any keyword as in the draft version of this proposal. The motivation for using any keyword at all stemmed partly from the desire to use a syntax that might be compatible with the non-template aliasing direction briefly outlined above.

This syntax was then adopted in the final proposal N2258 by Gabriel Dos Reis and Bjarne Stroustrup.

What is the difference between 'typedef' and 'using' in C++11?

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.



Typedef declarations can, whereas alias declarations cannot(+), be used as initialization statements

But, with the first two non-template examples, are
there any other subtle differences in the standard?

  • Differences in semantics: none.
  • Differences in allowed contexts: some(++).

(+) P2360R0 (Extend init-statement to allow alias-declaration) has been approved by CWG and as of C++23, this inconsistency between typedef declarations and alias declarations will have been removed.
(++) In addition to the examples of alias templates, which has already been mentioned in the original post.

Same semantics

As governed by [dcl.typedef]/2 [extract, emphasis mine]

[dcl.typedef]/2 A
typedef-name
can also be introduced by an
alias-declaration.
The identifier following the using keyword becomes a
typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. Such a
typedef-name has the same semantics as if it were introduced by the typedef specifier.
[...]

a typedef-name introduced by an alias-declaration has the same semantics as if it were introduced by the typedef declaration.

Subtle difference in allowed contexts

However, this does not imply that the two variations have the same restrictions with regard to the contexts in which they may be used. And indeed, albeit a corner case, a typedef declaration is an init-statement and may thus be used in contexts which allow initialization statements

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

whereas an alias-declaration is not an init-statement, and thus may not be used in contexts which allows initialization statements

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

Difference between typedef and C++11 type alias

There is absolutely no difference between both.

If you take a look at the standard :

7.1.3 The typedef specifier [dcl.typedef ]

A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name. It has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type and it shall not appear in the type-id.

7.3.3 The using declaration [namespace.udecl]

If a using-declaration uses the keyword typename and specifies a dependent name (14.6.2), the name introduced by the using-declaration is treated as a typedef-name.


However from this page : http://en.cppreference.com/w/cpp/language/type_alias

It is said :

Type aliases are similar to typedefs, however, have the advantage of working with templates.

It seems that this

// template type alias
template<class T> using ptr = T*;
// the name 'ptr<T>' is now an alias for pointer to T
ptr<int> x;

is only possible with the using directive.


And do not forget that this is a C++11 feature. Some compilers do not support it yet.

what is the use of using the typedef and what does it signifies?

Note, this is for C and not C++. Maybe it's applicable for C++ too, but I don't know.

typedef a b makes b an alias for a

What typedef struct alpha *Abc; is to make Abc an alias for struct alpha *. After you have created this, these two are equivalent:

struct alpha *ptr;
Abc ptr;

There are a few different uses for this:

It reduces how much you need to write. You can skip the struct word for instance. Many would say that this is NOT a good reason to use typedef.

It can give a more descriptive name for certain things. Examples:

typedef char* string; // I would never use this. It's just an example.
typedef int[3] vector; // However, this is something I would consider.

It can make it easier to change the type for a large code base in certain situations. Suppose you have this code:

int16_t foo(int16_t a)
{
int16_t b = a+1;
for(int16_t = 0; i<b; i++) {
...

And later you realize that it would be a good idea to change to int32_t instead. If the code instead looked like this, you would only need to change one line:

typedef int16_t T
T foo(T a)
{
T b = a+1;
for(T = 0; i<b; i++) {
...

Be restrictive with typedefs. Often they just clutters the code and makes it harder to follow. Use them when they fill a purpose. Be especially restrictive with aliasing pointers. I only use them for function pointers and completely opaque objects. Same thing with structs and unions. IMHO, 99% of the typedefs I see in questions here at SO are not necessary and seems to be there just because people think that they should.

If you consider typedefing a struct, ask yourself if it would be good to anyone reading the code to know that a declaration is a struct. If yes, declare it with the struct keyword. Of course you could invent something like typedef struct my_struct my_struct_s but is it really worth the effort? I'd say no.

It can be worth noting that C has something that seems like a simple form of namespace. This means that this completely valid:

typedef struct mystruct mystruct;
struct mystruct a;
mystruct b;

So mystruct is an alias for mystruct. And this is what you should do, unless you have a good reason not to.

I once wrote a related answer and if that answer wasn't so heavily focused on just structs, I would have made this question a duplicate.

In comments below, I saw this example:

typedef unsigned short int int_16;

and this is a HORRIBLE example. First, int_16 gives the impression that it is a signed type, which it is clearly not. Secondly, short int (and its unsigned counterpart) is guaranteed to have at least 16 bits, but int_16 gives the impression that it have exactly 16 bits. Depending on what you want, there are a few good ones in the standard:

  • int16_t and uint16_t - Exactly 16 bits
  • int_least16_t and uint_least16_t - At least 16 bits
  • int_fast16_t and uint_fast16_t - At least 16 bits, but hint to the compiler that you want the fastest type

What does passing two aliases to `typedef struct` mean?

I have seen this type of definition a lot in Microsoft code:

typedef struct {
int count;
char buffer[128];
} BUFFER, *PBUFFER;

It allows code like this to be written:

void read_buffer(PBUFFER pBuffer) {
// Do something with pBuffer
}

int main(void) {
BUFFER buffer;

read_buffer(&buffer);
return 0;
}

To directly answer your question: This kind of typedef allows a type and a pointer to a type to be defined at the same location in the code.

Is typedef in between the type and the alias standard-conformant?

TL/DR Version

Yes, int typedef INT is conforming.

James Michener Version

C declaration syntax (C 2011 online draft):

6.7 Declarations
Syntax
1    declaration:
        declaration-specifiers init-declarator-listopt ;
        static_assert-declaration
    declaration-specifiers:
        storage-class-specifier declaration-specifiersopt
        type-specifier declaration-specifiersopt
        type-qualifier declaration-specifiersopt
        function-specifier declaration-specifiersopt
        alignment-specifier declaration-specifiersopt
...

What this says is that in a single declaration you can have a sequence of one or more declaration specifiers, where each declaration specifier can be a storage class specifier (auto, extern, static, typedef), a type specifier (int, float, char, etc.), a type qualifier (const, restrict, volatile, etc.), a function specifier (inline), or an alignment specifier.

The order in which various specifiers appear doesn't matter; static const short int x; may be written as int static short const x, or int short const static x, etc. As a matter of practice, most people put the storage class specifier first, then any function or alignment specifiers (if necessary), then any type qualifiers, then type specifiers, so static const short int x is how most people would write that declaration.

This syntax is what allows us to write types like long double or long long or unsigned long int, etc.

Note that while the syntax allows arbitrary (and arbitrarily long) sequences of type specifiers, there's a semantic rule that only allows a relative few. You can't write short short short short x, for example, or long long long double y. Only the following sequences are allowed:

Constraints
2    At least one type specifier shall be given in the declaration specifiers in each declaration,
and in the specifier-qualifier list in each struct declaration and type name. Each list of
type specifiers shall be one of the following multisets (delimited by commas, when there
is more than one multiset per item); the type specifiers may occur in any order, possibly
intermixed with the other declaration specifiers.

        — void
        — char
        — signed char
        — unsigned char
        — short, signed short, short int, or signed short int
        — unsigned short, or unsigned short int
        — int, signed, or signed int
        — unsigned, or unsigned int
        — long, signed long, long int, or signed long int
        — unsigned long, or unsigned long int
        — long long, signed long long, long long int, or
            signed long long int
        — unsigned long long, or unsigned long long int
        — float
        — double
        — long double
        — _Bool
        — float _Complex
        — double _Complex
        — long double _Complex
        — atomic type specifier

        — struct or union specifier

        — enum specifier

        — typedef name

Addendum

As Keith points out in the comment below, a future revision of the language may limit storage class specifiers to the beginning of the declaration, so int typedef INT may not be legal under a future compiler.

Using typedef or using to define a structure - which is best?

Even better is to use neither. One type name should be enough. Pick either tagExportSettings or EXPORT_SETTINGS_S and stick with it. Example:

struct tagExportSettings
{
// ...
};

But, 1. All my code in the software uses EXPORT_SETTINGS_S

As I said, pick either name. If you use EXPORT_SETTINGS_S, then name the class as EXPORT_SETTINGS_S:

struct EXPORT_SETTINGS_S
{
// ...
};

If something still refers to tagExportSettings, then refactor the code to use the canonical name.


But more generally, using is preferred to typedef because it's more readable. There are at least two reasons for it:

  1. With typedef syntax it isn't intuitive which is the old name and which is the new alias:

    typedef new_or_old old_or_new; // old_or_new is the new alias

    using is intuitive through familiar pattern of initialisation:

    using intuitively_alias = intuitively_old_name;
  2. typedef syntax is difficult for a programmer to parse in case of compound names because the alias is "interleaved":

    // alias for void()
    typedef void function();
    using function = void();

What was the issue solved by the new using syntax for template typedefs?

From the WG21 proposal N1489 Template aliases (by Stroustrup and Dos Reis):

It has been suggested to (re)use the keyword typedef as done in the
paper [4] to introduce template aliases:

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

That notation has the advantage of using a keyword already known to
introduce a type alias. However, it also displays several disavantages
among which the confusion of using a keyword known to introduce an
alias for a type-name in a context where the alias does not designate
a type, but a template; Vec is not an alias for a type, and should not
be taken for a typedef-name. The name Vec is a name for the family
std::vector<o, MyAllocator<o> > where the bullet is a placeholder
for a type-name. Consequently we do not propose the typedef syntax.

On the other hand the sentence

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

can be read/interpreted as: from now on, I'll be using Vec<T> as a
synonym for std::vector<T, MyAllocator<T> >. With that reading, the
new syntax for aliasing seems reasonably logical.

The paper [4] referred to in the above quote was a prior proposal WG21 N1406 Proposed Addition to C++: Typedef Templates (by Herb Sutter). It uses both a different syntax (typedef vs using) as well as a different nomenclature (typedef templates vs template aliases). Herb's proposed syntax didn't make it, but the nomenclature can sometimes be found in informal discussions.



Related Topics



Leave a reply



Submit