Typedefs allow the programmer to create an alias for a data type, and use the aliased name instead of the actual type name. Typedef literally stands for, “type definition”.
To declare a typedef, simply use the typedef
keyword, followed by the type to alias, followed by the alias name:
1 2 3 4 5 |
typedef double distance_t; // define distance_t as an alias for type double // The following two statements are equivalent: double howFar; distance_t howFar; |
By convention, typedef
names are declared using a “_t” suffix. This helps indicate that the identifier represents a type, not a variable or function, and also helps prevent naming collisions with other identifiers.
Note that a typedef
does not define a new type. Rather, it is simply an alias (another name) for an existing type. A typedef
can be used interchangeably anywhere a regular type can be used.
Even though the following does not make sense semantically, it is valid C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
int main() { typedef long miles_t; typedef long speed_t; miles_t distance { 5 }; speed_t mhz { 3200 }; // The following is valid, because distance and mhz are both actually type long distance = mhz; return 0; } |
typedef
s and type aliases follow the same scoping rules as variables. The typedef
s miles_t
and speed_t
are only usable in the main()
function. If they were placed in another function, main()
wouldn’t be able to access them. If they were placed outside of main()
, in the global scope, all functions would be able to access them.
However, typedefs have a few issues. First, it’s easy to forget whether the type name or type definition come first. Which is correct?
1 2 |
typedef distance_t double; // incorrect typedef double distance_t; // correct |
I can never remember.
Second, the syntax for typedefs gets ugly with more complex types, particularly function pointers (which we will cover in future lesson 10.9 -- Function Pointers):
Type aliases
To help address these issues, an improved syntax for typedefs
has been introduced that mimics the way variables are declared. This syntax is called a type alias.
Given the following typedef:
1 |
typedef double distance_t; // define distance_t as an alias for type double |
This can be declared as the following type alias:
1 |
using distance_t = double; // define distance_t as an alias for type double |
The two are functionally equivalent.
Note that although the type alias syntax uses the “using” keyword, this is an overloaded meaning, and does not have anything to do with the using statements
related to namespaces. As such, it is not affected by the rule of not using “using namespace”.
This type alias syntax is cleaner for more advanced typedefing cases, and should be preferred.
Using type aliases for legibility
One use for type aliases is to help with documentation and legibility. Data type names 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, consider the following function:
1 |
int GradeTest(); |
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! Int does not tell us anything.
1 2 |
using testScore_t = int; testScore_t GradeTest(); |
However, using a return type of testScore_t makes it obvious that the function is returning a type that represents a test score.
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 shorts
were being used to hold ID numbers and which were being used for other purposes.
However, with a type alias, all you have to do is change using studentID_t = short;
to using studentID_t = long;
. However, caution is still necessary 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.
Using type aliases for platform independent coding
Another advantage of 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:
1 2 3 4 5 6 7 8 9 |
#ifdef INT_2_BYTES using int8_t = char; using int16_t = int; using int32_t = long; #else using int8_t = char; using int16_t = short; using int32_t = int; #endif |
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.
This is exactly how the fixed width integers, like std::int8_t
(covered in lesson 4.6 -- Fixed-width integers and size_t), are defined!
This is also where the issue with int8_t
being treated as a char
comes from -- std::int8_t
is a type alias of char
, and thus is just an alias for a char
rather than being a unique type. As a result:
1 2 3 4 5 6 7 8 9 10 |
#include <cstdint> // for fixed-width integers #include <iostream> int main() { std::int8_t i{ 97 }; // int8_t is actually a type alias for signed char std::cout << i; return 0; } |
This program prints:
a
not 97, because std::cout prints char
as an ASCII character, not a number.
Using type aliases to make complex types simple
Although we have only dealt with simple data types so far, in advanced C++, you could see a variable and function declared like this:
1 2 3 4 5 6 |
std::vector<std::pair<std::string, int> > pairlist; bool hasDuplicates(std::vector<std::pair<std::string, int> > pairlist) { // some code here } |
Typing std::vector<std::pair<std::string, int> >
everywhere you need to use that type can get cumbersome. It’s much easier to use a type alias:
1 2 3 4 5 6 7 8 |
using pairlist_t = std::vector<std::pair<std::string, int> >; // make pairlist_t an alias for this crazy type pairlist_t pairlist; // instantiate a pairlist_t variable bool hasDuplicates(pairlist_t pairlist) // use pairlist_t in a function parameter { // some code here } |
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 those types easier to work with and understand.
Best practice
Favor type aliases over typedefs, and use them liberally to document the meaning of your types.
Quiz time
Question #1
Given the following function prototype:
1 |
int printData(); |
Convert the int return value to a type alias named error_t. Include both the type alias statement and the updated function prototype.
![]() |
![]() |
![]() |
Hi you din't mention if type alias statement should be used before int main() in global namespace or inside the main function?
Under Question 1, i made it into a full program .If i use type alias inside main function it wont compile. It compiles only if i use type alias statement in global namespace before main function like this.
#include<iostream>
using error_t = int;
error_t printData()
{
return 1;
}
int main()
{
printData();
std::cout << printData() << '\n';
return 0;
}
Why is that happening ?
Type aliases follow the same scoping rules as variables. I added a sentence after the second example.
hello ALEX
What does _w64 do in above phrase ....same goes for
*PULONG_PTR
thanks very much
https://docs.microsoft.com/en-us/cpp/cpp/w64?view=vs-2019
This typedef is defining two types, a non-pointer type, and a pointer type. PULONG_PTR is the name of the pointer type.
I just wanted to point out that I was unable to use uniform initialization (aka {}) to initialize the type alias.
`error_t` isn't a variable, you're not initializing anything. Type aliases use `=`.
Hi Alex, thanks so much for this amazing course!!
I noticed that in the typedef statement, the word "type" comes before "def", which might help to remember that the type name comes before the type definition :)
Just my $2.0e-2
ith
Hi Guys , This is an awesome tutorial. But I got stuck here, Under "Using typedefs for easier code maintenance" you have clearly said to change one type to another all you have to do is change "typedef short studentID_t" to "typedef long studentID_t". But this did not work for me,My code does not compile and shows "Error C2371 'data': redefinition; different basic type"
This is my code
Kindly help me find my flaw here thanks!
You cannot re-define aliases. Once you set "data" as an alias for "float", it will stay like that and cannot be changed.
But buddy, In the section "Using typedefs for easier code maintenance" It says I can change the underlying Type if I want to ?
If you decide that @studentID_t should no longer be a short, but a float, you go to the typedef and modify it. You can't copy it and modify the copy.
Ah This makes sense, Thank you @nascardriver. Most people do not take time to answer simple questions like this, I appreciate you for taking the time into helping me understand this Thanks :)
What is the diference between
and
Also:
@__int32 is not a standard type. Everything that contains a double underscore is a reserved name. If you encounter such an entity, don't use it. It might not exist for other people.
@int32_t is @std::int32_t. It's a signed int type, 32 bits in size. It's unsigned counterpart is @std::uint32_t.
Neither of those is guaranteed to be defined. Use @std::int_fast32_t or @std::int_least32_t.
Sir, I dont understand where we're going. Is this tutorial directed only at console application development? When will I be able to create a program inside its own window, with buttons and similar matters?
GUIs are OS-specific. learncpp is about the language. You need the contents of learncpp to be able to build GUIs, but you won't learn it here.
I see, thank you for replying
Hi Thank you for the tutorial,Could any one please explain me how the below code is working
I am getting output as :
0 1 2 3 4 5 6 7 8 9 10 11
0 1 2 3 4 5 6 7 8 9 10 11
Please explain by using "using" how the 2D array is printing the output.
:(
What exactly are you having a problem with?
Line 2 only allows Line 11, 17 and 18 to omit "std::".
I think the question how the "using" works is referring to line 8:
and the use of int_array in the following for-loop.
I can't answer it because I'm not familiar with this kind of for-loops.
The 'using' in Line 2 is applicable to Lines 11, 17 and 18 to omit "std::". [This is related to namespaces]
However , the 'using' in Line 8 is related to type-aliasing where user can type int_array wherever he needs to use int[4].
In Line 9, we are using a C++ 11 concept of Range-based for-loops.
Please refer : https://www.learncpp.com/cpp-tutorial/6-12a-for-each-loops/
1a:
1b:
Is best if I define (or declare) type alias in particular header file?
Hi Samira!
Declare it with the smallest scope possible which still does the job. If you need the type alias all over the place then declaring it in a header is the correct choice.
Put this in the lesson please!
This isn't really specific to typedefs -- all objects, functions, and types (including type aliases) should be defined in as small a scope as possible.
Help please! I read the Platform independent coding 4 times and I still have problems with it. Isn't those aliases already defined (int8_t, int32_t, etc) ? Or only are those used in C++11 and using #include <cstdint>? If int8_t is char in C++ wont there be a problem in the program, when doing divisions and multiplications and what not, since char and int are stored in two different forms?
also last question, what is in defined in INT_2_BYTES and would it be in a separate file?
any help is appreciated
Those aliases are now formally defined in C++11 using cstdint.
In C++, char is considered an integral value and stored using the same format as a normal int. Where you run into problems is interpretation, where int8_t gets interpreted as a char instead of an int since the compiler can't tell the difference (mostly an issue when outputting).
INT_2_BYTES is something I made up just to show how the typedefs map to the fundamental types -- how that's defined or not is up to the compiler/implementation.
Hi Alex!
a conclusion at the end would be nice.
Hi Alex, in the two examples under the paragraph named:
"Using typedefs to make complex types simple",
you used the keyword "boolean" instead of bool.
- when used in conjunction with the preprocessor
how to determine which set of typedefs to execute,
using sizeof() maybe ?
Hi David!
> how to determine which set of typedefs to execute
Output
d
The output might be different when using another compiler. You can also compare two types:
@sizeof can be used when you know all possible data types have different sizes. It doesn't help when two data types have the same size.
Thanks for pointing out the typo. Fixed!
Yes, sizeof() can be used. If you check out http://www.azillionmonkeys.com/qed/pstdint.h, they use sizeof() and other techniques to deduce the size of different types.
Could someone tell me where´s the error, please? I was attempting to create a Fibonacci serie:
Hi Weckersduffer!
I fixed your code to an extend that it can be compiled, it still is an infinity loop and doesn't print the fibonacci sequence, I'll leave that up to you.
If you need further help feel free to ask.
Thanks, nascardriver. You're always so helpful
Really? my bet was on the while(true) bit
Why isn't it working?
Hi Hans!
Thanks a lot, i appreciate it!
I only named the function printData because it was named like this in the Quiz above.
Why not use
?
Because use of macros is discouraged. This is covered in lesson 2.9.
Hey Alex, great lessons!
Found a typo:
[...] and should be preferred if ___you___ compiler is C++11 capable.
"you" is meant as "your".
Typo fixed. Thanks!
Typo right above the "Type aliases in C++11" header:
... that typedefs allow you to take complex types and GIVEN them a simple name, ...
I think should be:
... that typedefs allow you to take complex types and GIVE them a simple name, ...
I have three files:
main.cpp, functions.cpp, funcitons.h
if I use
in my main.cpp and functions.cpp
and use
in functions.h
what will the scope of that typedef be? will 'miles' be treated as 'int' in my main.cpp?
#include just copies the included file at the point of inclusion. So having main.cpp and functions.cpp #include functions.h copies the typedef into those files at the point of inclusion. That typedef is available until the end of each of those files respectively.
Is this correct?
We first need to know the size of all data types on any specific platform and then to edit the preprocesser accordingly. For example, int is of size 8 bytes on some platform, then we must have the conditional compilation content according to it.
If knowing the specific size of an integer is important, you're better off using the fixed-size integers introduced in C++11 than messing with a bunch of preprocessor stuff.
Hi,
I have come across the below piece of code.Can you please clarify the difference between ItemType_e and ItemType_t here.
typedef enum ItemType_e
{
ITEMTYPE_SWORD,
ITEMTYPE_TORCH,
ITEMTYPE_POTION
}ItemType_t;
Also in certain cases for user defined type that are to be typedef'ed they ignore the UserDefinedTypeName in the following syntax:
typedef userDefinedTypeKeyWord UserDefinedTypeName { userDefinedTypeDeclaration; } NewUserDefinedTypeName_t; //userDefinedTypeKeyWord includes class, struct, enum, etc.
so in your case you may also see the syntax like this:
typedef enum
{
ITEMTYPE_SWORD,
ITEMTYPE_TORCH,
ITEMTYPE_POTION
}ItemType_t;
The doubt I have is this: if we use typedef for userdefined type isnt it the same as the userdefined type themselves?
Isn't:
typedef struct { int value;} StructName;
same as:
struct StructName {int value;};
Then what is the purpose of typedef in the case of user defined types?
@Manik
if you have already declared your enum then you can also do this:
enum ItemType_e
{
ITEMTYPE_SWORD,
ITEMTYPE_TORCH,
ITEMTYPE_POTION
};
typedef ItemType_e ItemType_t;
which does the exact same thing as your code.
I am also learning right now and i just realised my first answer (May 11, 2017 at 9:58 pm) is wrong, how can i delete it now?I thought it would be Admin regulated Post. Sorry for it.
The correct answer to your question is:
ItemType_e is the type name under which the enum declaration is constructed,
ItemType_t is the type alias for the ItemType_e ( and it is not an object ).
std::vector<std::pair<std::string, int>>
I've just finished section 4.6 and am loving the tutorials, but...did I miss something? Or am I not supposed to know exactly what this means yet? I think the only thing I skipped was bitwise operators to this point. I know the std stands for the standard namespace. At first glance, vector and pair both look like functions and std::string, int are the parameters of the function pair.
If this is covered soon hereafter, I guess just point me to the lesson or I'll come across it sooner than later. Otherwise, if it were covered before this lesson, please refresh my memory because I'm not seeing exactly what I think I'm supposed to at the moment.
Edit: After reading further, it appears now to me that vector and pair are just types in the std namespace, but I guess I'm still confused about the brackets and what they mean in terms of connecting them all. Any help would be appreciated, thanks.
No, you didn't miss anything. The key takeaway here is just that typedes allow you to assign a simple alias to something that can potentially be much more complicated. You don't need to know anything about std::vector, std::pair, or all those crazy angle brackets yet. We'll cover those in due time.
In modern c++ this would be usually be written as
Use of using rather than typedef is now preferred.
Thanks for the note. I've added a section to the lesson to cover this, as well as a short quiz.
In this where exactly is INT_2_BYTES defined? It can't be in our program as we are unaware of the size of integer in any specific system.
You'd have to define it in your program, somewhere above this point (or define it as a compiler flag as part of your project).
How to determine whether integers are 2 bytes or not as part of the preprocessing phase is more complicated (but there are ways to do so, otherwise libraries like pstdint.h couldn't exist.
Typo.
"A typedef does not define (a) new type."
Also updated. Thanks!
Hi!
I want to thank you for these tutorials. They are extremely helpful, concise and clear. I've been helped by them in many ways and every time I read them it seems like I'm learning something simple. I'm grateful for people like you, who want to share what they know and know how to do it.
Keep up the good work! :)
Learning from you,
Daniel.
An alternative to using typedefs would of course be using comments, for example:
I prefer using comments for at least three reasons:
1. You only need one line of code (if you, like me, want a linebreak after each semicolon, that is).
2. There's one keyword (typedef) less to learn how to use, and you can learn to use something else instead.
3. The risk of making a mistake is greatly reduced. In other words, if you make a typo inside a comment it doesn't matter while it usually does if you make one outside a comment.
I understand the typedef stuff ok but the :
This just lost me good.
Where is the if else coming from. If "what" or else "why". Is the preprocessor comparing something? Or is this an example of code that could be written?
Your tutorial is great, it is filling in a lot of holes I had in my attempt to teach myself c++. My problem is that my brain has very little RAM. I guess I chewed on too many lead bars when I was kid. It was ok back then to chew on lead bars, 60 years ago you didn't need brains like today. I was going to do a site on learning c++ but I can't beat this site so I will put links on my sites leading to yours. This is pro stuff you got, real education taught by an educator.
thanks
We cover most of this in lesson 1.10 -- a first look at the preprocessor and header guards.
Basically, the preprocessor is checking to see whether INT_2_BYTES has been #defined somewhere. This could be defined in your program somewhere, or passed to the compiler directly (all compilers have a way for the user to pass in defines directly). If it has been defined, the top set of code is compiled. If not, the bottom set of code is compiled. This is called "conditional compilation", if you want to do more reading on it.
I am a little confused too.
If we use this conditional code because we don't know what type of system the program will end up running on(whether an int will be 2 bytes or 4 bytes), then at what point in coding would we be able to make the decision to #define INT_2_BYTES?
Or are you saying that we in fact would not know beforehand which set of typdefs to compile, and that we would have to compile the program twice to make two different versions for two different systems?
Most likely you'd do this when you're distributing your source code. That way, someone on a machine that has 2 byte integers could #define INT_2_BYTES and compile your source code, producing an executable that runs correctly on their machine.
Ok I wrote a program like this to calculate distance in km and I used DistanceCovered as alias for the type double:
It is returning me something like this when I run it in the g++ compiler:
/usr/lib/gcc/i486-linux-gnu/4.3.3/../../../../lib/crt1.o: In function `_start':
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:115: undefined reference to `main'
I'm sure something is wrong with my code but I can'tfind what!
Well what is obviously missing is the int main() section. I took the code and copied it and added the int main() section and it worked perfectly.
Is there any reason typedefs should be used rather than #defines?
typedefs are generally safer and harder to inadvertently screw up than #defines.
Here's an example of where using a typedef is better than using a #define:
In the second example, you were probably expecting pW to be of type int*, but it's of type int instead!
I would think this:
#define int* intptrdef;
intptrdef pZ, pW;
translates to this:
int* pZ, pW
in which case both should be pointers.
You would think so, but "int* pZ, pW" declares an integer pointer named pZ and an integer named pW.
Think of the asterisk that defines a pointer-to-type as being part of the identifier and not the type specifier. Which is why some prefer the spacing
to