Search

8.6 — Typedefs and type aliases

The typedef keyword

In C++, typedef (which is short for “type definition”) is a keyword that creates an alias for an existing data type. To create such an alias, we use the typedef keyword, followed by an existing data type to alias, followed by a name for the alias. For example:

By convention, typedef names typically use a “_t” suffix. This both indicates that the name is a typedef, and helps prevent naming collisions with other types of identifiers.

Best practice

Name your typedefs with a _t suffix, to indicate that the name is a typedef and help prevent naming collisions with other types of identifiers.

Once defined, a typedef name can be used anywhere a type is needed. For example, we can create a variable with the typedef name as the type:

.

When the compiler encounters a typedef name, it will substitute in the aliased type. For example:

This prints:

3.4

In the above program, we first define typedef distance_t as an alias for type double.

Next, we define a variable named milesToDestination of type distance_t. Because the compiler knows distance_t is a typedef, it will use the aliased type, which is double. Thus, variable milesToDestination is actually compiled to be a variable of type double, and it will behave as a double in all regards.

Finally, we print the value of milesToDestination, which prints as a double value.

Typedefs are not new types

A typedef does not actually define a new type -- it just introduces a new identifier for an existing type. A typedef is completely interchangeable with the aliased type.

This allows us to do things that are syntactically valid but semantically meaningless. For example:

Although conceptually we intend miles_t and speed_t to have distinct meanings, both are just typedefs for type long. This effectively means values of type miles_t, speed_t, and long can all be used interchangeably. And indeed, when we assign a value of type speed_t to a variable of type miles_t, the compiler only sees that we’re assigning a value of type long to a variable of type long, and it will not complain.

Warning

Care must be taken not to mix values of typedefs that are intended to be semantically distinct.

As an aside...

Some languages support the concept of a strong typedef. A strong typedef actually creates a new type that has all the original properties of the original type, but the compiler will throw an error if you try to mix values of the aliased type and the strong typedef. As of C++20, C++ does not directly support strong typedefs (though enum classes, covered in lesson 9.3 -- Enum classes, are similar), but there are quite a few 3rd party C++ libraries that implement strong typedef-like behavior.

The scope of typedefs

Because scope is a property of an identifier, typedef identifiers follow the same scoping rules as variable identifiers: a typedef defined inside a block has block scope and is usable only within that block, whereas a typedef defined in the global namespace has file scope and is usable to the end of the file. In the above example, miles_t and speed_t are only usable in the main() function.

If you need to use one or more typedefs across multiple files, they can be defined in a header file and #included into any code files that needs to use the definition:

mytypes.h:

Typedefs #included this way will be imported into the global namespace and thus have global scope.

Typedef issues

Typedefs have a few syntactical issues. First, it’s easy to forget whether the typedef name or aliased type name come first. Which is correct?

It’s easy to get backwards. Fortunately, in such cases, the compiler will complain.

Second, the syntax for typedefs can get ugly with more complex types. For example:

In the above typedef definition, the name of the new type (fcn_t) is buried in the middle of the definition, making the definition hard to read.

Type aliases

To help address the above-mentioned syntactical issues, an improved syntax for defining typedefs was introduced, called a type alias.

Given the following typedef:

The equivalent type alias syntax looks like this:

Type aliases are functionally equivalent to typedefs, but come with the benefit of a nicer definition syntax.

Here is the hard-to-read typedef we introduced above, along with an equivalent (and slightly easier to read) type alias:

Best practice

When creating aliased types, prefer the type alias syntax over the typedef syntax.

When should we use type aliases?

Now that we’ve covered what typedefs and type aliases are, let’s talk about what they are useful for.

Using type aliases for platform independent coding

One of the main use for type aliases is that they can be used to hide platform specific details. On some platforms, an int is 2 bytes, and on others, it is 4 bytes. Thus, using int to store more than 2 bytes of information can be potentially dangerous when writing platform independent code.

Because char, short, int, and long give no indication of their size, it is fairly common for cross-platform programs to use type aliases to define aliases that include the type’s size in bits. For example, int8_t would be an 8-bit signed integer, int16_t a 16-bit signed integer, and int32_t a 32-bit signed integer. Using type aliases in this manner helps prevent mistakes and makes it more clear about what kind of assumptions have been made about the size of the variable.

In order to make sure each aliased type resolves to a type of the right size, type aliases of this kind are typically used in conjunction with preprocessor directives:

On machines where integers are only 2 bytes, INT_2_BYTES can be #defined, and the program will be compiled with the top set of type aliases. On machines where integers are 4 bytes, leaving INT_2_BYTES undefined will cause the bottom set of type aliases to be used. In this way, int8_t will resolve to a 1 byte integer, int16_t will resolve to a 2 bytes integer, and int32_t will resolve to a 4 byte integer using the combination of char, short, int, and long that is appropriate for the machine the program is being compiled on.

The fixed-width integers (such as std::int_fast16_t and std::int_least32_t) and size_t type (both covered in lesson 4.6 -- Fixed-width integers and size_t) are actually just type aliases to various fundamental types.

This is also why when you print an 8-bit fixed-width integer using std::cout, you’re likely to get a character value. For example:

This program prints:

a

Because std::int_least8_t is typically defined as a type alias for one of the char types, variable x will be defined as a char type. And char types print their values as ASCII characters rather than as integer values.

Using type aliases to make complex types simple

Although we have only dealt with simple data types so far, in advanced C++, types can get complicated and lengthy to type. For example, you might see a function and variable defined like this:

Typing std::vector<std::pair<std::string, int> > everywhere you need to use that type is cumbersome and easy to typo. It’s much easier to use a type alias:

Much better! Now we only have to type pairlist_t instead of std::vector<std::pair<std::string, int> >.

Don’t worry if you don’t know what std::vector, std::pair, or all these crazy angle brackets are yet. The only thing you really need to understand here is that type aliases allow you to take complex types and give them a simple name, which makes your code easier to read and saves typing.

This is probably the best use for type aliases.

Using type aliases for legibility

Type aliases can also help with code documentation and comprehension.

With variables, we have the variable’s identifier to help document the purpose of the variable. But consider the case of a function’s return value. Data types such as char, int, long, double, and bool are good for describing what type a function returns, but more often we want to know what purpose a return value serves.

For example, given the following function:

We can see that the return value is an integer, but what does the integer mean? A letter grade? The number of questions missed? The student’s ID number? An error code? Who knows! The return type of int does not tell us much. If we’re lucky, documentation for the function exists somewhere that we can reference. If we’re unlucky, we have to read the code and infer the purpose.

Now let’s do an equivalent version using a type alias:

The return type of testScore_t makes it a little more obvious that the function is returning a type that represents a test score.

In our experience, creating a type alias just to document the return type of a single function isn’t worth it (use a comment instead). But if you have already created a typedef for other reasons, this can be a nice additional benefit.

Using type aliases for easier code maintenance

Type aliases also allow you to change the underlying type of an object without having to change lots of code. For example, if you were using a short to hold a student’s ID number, but then later decided you needed a long instead, you’d have to comb through lots of code and replace short with long. It would probably be difficult to figure out which objects of type short were being used to hold ID numbers and which were being used for other purposes.

However, if you use type aliases, then changing types becomes as simple as updating the type alias (e.g. from using studentID_t = short; to using studentID_t = long;).

While this seems like a nice benefit, caution is necessary whenever a type is changed, as the behavior of the program may also change. This is especially true when changing the type of a type alias to a type in a different type family (e.g. an integer to a floating point value, or vice versa)! The new type may have comparison or integer/floating point division issues, or other issues that the old type did not. If you change an existing type to some other type, your code should be thoroughly retested.

Downsides and conclusion

While type aliases offer some benefits, they also introduce yet another identifier into your code that needs to be understood. If this isn’t offset by some benefit to readability or comprehension, then the type alias is doing more harm than good.

A poorly utilized type alias can take a familiar type (such as std::string) and hide it behind a custom name that needs to be looked up. In some cases (such as with smart pointers, which we’ll cover in a future chapter), obscuring the type information can also be harmful to understanding how the type should be expected to work.

For this reason, type aliases should be used primarily in cases where there is a clear benefit to code readability or code maintenance. This is as much of an art as a science. Type aliases are most useful when they can be used in many places throughout your code, rather than in fewer places.

Best practice

Use type aliases judiciously, when they provide a clear benefit to code readability or code maintenance.

Quiz time

Question #1

Given the following function prototype:

Convert the int return value to a type alias named error_t. Include both the type alias statement and the updated function prototype.

Show Solution


8.7 -- Type deduction for objects using the auto keyword
Index
8.5 -- Explicit type conversion (casting) and static_cast

114 comments to 8.6 — Typedefs and type aliases

  • Rishi

    Hi! You're doing a great job. It's nice to see those updates you're making. There's this line in this lesson:

    ✓"Typedefs have a few syntactical issues. First, it’s easy to forget whether the type name or type definition come first. Which is correct?"

    If possible, add this sentence here:

    ✓"You could remember the typedef syntax as the data type name first("type" part of "typedef") and the definition name as the second ("def" part of "typedef") parameters."

    That's it. Thank you, have a nice day :)

  • Charles

    It seems that C++ allows you to do:

    . This will create Point as an alias for float[3]. You can then create an array of Point like

    .

  • Ronny

    What's the difference between typedef and const then?

  • kio

    Hi Nascar,
    Where to declare type alias, on the beginning of the file or when needed like inside the function or block of code?

  • Waldo Lemmer

    Thanks for another great lesson! Very well explained

    Type aliases seem to be incredibly useful. Is it a C++ only feature? I don't recall seeing it in any other programming languages (but then again, this is the furthest that I've gotten into learning a programming language).

    Section "Using type aliases for platform independent coding":
    > This is exactly how the fixed width integers (like int8_t) that were introduced in C++11
    You might want to remove "that were introduced in C++11" as per [your comment](https://www.learncpp.com/cpp-tutorial/the-auto-keyword/#comment-460178).

    P.S. have you considered adding Markdown functionality to comments? That would make comments look much prettier :D

  • dede

    One use for type aliases is to help with documentation and legibility.
    what documentation means ?

  • Patrick

    To remember the order of a typedef:
    typedef double distance_t;// _t = trailing edge

    Regards,
    Patrick

  • sami

    Hi,
    I was wondering why 'short' in the top set and 'long' in the bottom set weren't defined!

    • nascardriver

      At the top `short` is either 8 or 16 bits, but we already have types with those sizes, so we don't need `short`.
      At the bottom, `long` would be 32 and 64 bits. We already have `int` for 32 bits, and the example doesn't cover 64 bits, so we don't need `long`.

  • Toka

    You initialised the variable i using (). Is that a mistake or I am understanding wrong?

    Since it should be a function if it has () and not a variable.

    • sami

      It is another way of initializing variables in C++, called direct initialization.

      See the link below for more information:

      https://www.learncpp.com/cpp-tutorial/variable-assignment-and-initialization/

    • nascardriver

      @sami is right, this is the direct initialization syntax. As per best practice, I updated the lesson to use list initialization instead.

  • Gowtham

    I was wondering why the following code is working: as you mentioned, 'If they were placed outside of main(), in the global scope, all functions would be able to access them.' But I defined exactly the same type alias!

  • Alek

    Hello,could you tell me how below code dinstinguishes whether a pc defines int as 4 bits or 2 bits ?:

    no matter what, but whatever I think about this block of code alone cannot do that.then could you tell me what else has to be done in addition to this ?

    • Alek

      Can you please take a look at this ?
      I mean how/where an IDE defines INT_2_BYTES so this code makes sense if this is exactly how it is defined.
      I dunno if I could make myself clear or not but take a look at this if possible.
      Best Regards!

    • Alek

      can you please take a look at this ?
      I mean how the compiler knows which set of type aliases to use by just this #ifdef directive ?shouldn't INT_2_BYTES be defined somewhere b4 this can happen ?
      best regards!

      • nascardriver

        Compilers can define macros, they don't have to be defined in code. The compiler can figure out what architecture it is running on and define a macro accordingly.

  • Fan

    It is useful to mention that the same typedef can appear multiple times in a single file.

  • Snownly

    Hi! Fantastic website so far, really helping me get through this lockdown haha!

    Just a quick question regarding the last section. When I enter the complex type "std::vector<std::pair<std::string, int> >" it results in a semantic error "Implicit instantiation of undefined template 'std::__1::vector<std::__1::pair<std::__1::basic_string<char>, int>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char>, int> > >'". How would I fix this?

    • nascardriver

      Can you provide an example that you think should compile, but doesn't? Telling from the error message alone, you might be missing one of the includes

  • koe

    Do typedefs/aliases need to be #included in header files wherever you want to use them? Might be good to add a comment to this chapter :)

  • Behzad

    Is there any way to declare an alias for a data members? For example, a derived class declares an alias for a member in its base class, without using any pointer or reference, so that no additional memory is used.

    • nascardriver

      No. You can add a function that returns a reference to the member of the parent.

      Your situation sounds like you have public data members. Try to avoid that. You might be allowing the user of your class to break it.

  • pi13

    If I am only using my computer, which defines int as 4 bytes, and I compile the code into an exe file, then I send the exe to a friend whose computer defines int as 2 bytes, will there be any problems? From my understanding on how it works, there wouldn’t be any problems.
    So do I only need to do:

    if I was working on the same project on different computers (i.e. the project could be a library like a game engine that a person would release to work for anyone to program with, if I’m working with others who have different computers on the same project, compiling it on different os, etc.)?

    • nascardriver

      Use the fixed-width types from lesson 4.6.
      Types don't change after compilation. If you compiled the code with an int being 16 bits wide, nothings going to change that. The program will use a 16 bit int on your friend's computer as well (If your friend's computer can execute the program).

  • Patrick

    Hey, thanks for the very thorough tutorials.

    I tried to define a type alias like this

    This results in "error: expected type-specifier before 'static'".

    Does anybody know why this does not work?

    Can keywords like "static" or "constexpr" even be used in a type alias definition?

    • nascardriver

      > Can keywords like "static" or "constexpr" even be used in a type alias definition?
      No, they can't. That would makes declarations very confusing.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]