Question
In C++11, using can be used to create a type alias in a way that looks similar to typedef.
For example:
typedef int MyInt;
appears equivalent to:
using MyInt = int;
I also understand that this newer syntax was introduced partly to support alias templates, such as:
template<class T>
using MyType = AnotherType<T, MyAllocatorType>;
For the simple non-template examples above, are there any subtle differences defined by the C++ standard?
For example, typedef does not create a new type; it only creates another name for an existing type, so conversions remain the same. Is using the same in this respect, or does it create a distinct type? Are there any practical differences between them?
Short Answer
By the end of this page, you will understand that both typedef and using create type aliases, not new types. You will also learn the main practical difference: using has cleaner syntax and can create alias templates, while typedef cannot. This makes using the preferred choice in modern C++ code.
Concept
Both typedef and using let you give an existing type a new name.
typedef int MyInt;
using MyInt = int;
In both cases:
MyIntis just another name forint- no new type is created
- implicit conversions behave exactly as they do for
int - the compiler treats the alias as the original type
So if you write:
using Age = int;
Age a = 25;
int b = a;
there is no special conversion happening. Age and int are the same type.
Why this matters
Type aliases are useful because they make code:
- easier to read
- easier to refactor
- less repetitive
- clearer when working with complex template types
For example, a container type can be long and noisy:
std::vector<std::pair<std::string, int>> data;
An alias can make intent clearer:
using ScoreList = std::vector<std::pair<std::string, int>>;
ScoreList data;
The key difference
For non-template aliases, typedef and using are functionally equivalent.
The important difference is syntax and template support:
typedefuses older C-style declaration syntaxusinguses a more readablename = typestyleusingcan define alias templatestypedefcannot directly define alias templates
Example:
template<class T>
using Vec = std::vector<T>;
This is valid and common in modern C++.
There is no equivalent direct typedef form for this.
Does either create a new type?
No. Neither typedef nor using creates a distinct type.
If you need a truly separate type, you must define one explicitly, for example:
struct UserId {
int value;
};
Now UserId is a real new type, unlike an alias such as using UserId = int;.
Mental Model
Think of a type alias like adding a nickname to a person.
- The person's real identity does not change.
- You are just calling them by another name.
If int is the person, then:
typedef int MyInt;
using MyInt = int;
are both just nicknames for the same person.
A new type, however, is like creating a completely different person with their own identity.
struct MyInt {
int value;
};
That is no longer a nickname. That is a different type.
So the mental rule is:
typedef= nicknameusing= nicknamestruct/class/enum class= new type
Syntax and Examples
Basic syntax
typedef
typedef existing_type AliasName;
Example:
typedef unsigned long ulong;
ulong count = 10;
using
using AliasName = existing_type;
Example:
using ulong = unsigned long;
ulong count = 10;
These two examples mean the same thing.
Complex type example
With function pointers, using is often easier to read.
With typedef
typedef void (*Handler)(int);
With using
using Handler = void (*)(int);
Both define Handler as a pointer to a function that takes an int and returns void.
Alias templates
This is where using becomes much more powerful.
#include <vector>
template<class T>
using Vec = std::vector<T>;
Vec<int> numbers = {1, 2, 3};
Vec<std::string> names = {"A", "B"};
You cannot write the same kind of alias template directly with typedef.
Alias does not create a new type
#include <iostream>
using Age = int;
int main() {
Age a = 30;
int b = a;
std::cout << b << '\n';
}
This works because Age is exactly int, not a separate type.
Step by Step Execution
Consider this example:
#include <iostream>
using MyInt = int;
int addOne(MyInt value) {
return value + 1;
}
int main() {
MyInt x = 5;
int y = addOne(x);
std::cout << y << '\n';
}
Step-by-step
-
using MyInt = int;- The compiler creates a type alias.
MyIntbecomes another name forint.
-
int addOne(MyInt value)- Since
MyIntis justint, this function is effectively:
int addOne(int value) - Since
-
MyInt x = 5;xis really anintvariable.
-
int y = addOne(x);xis passed toaddOneas anint.- The function returns
6.
-
std::cout << y << '\n';- Prints
6.
- Prints
Important takeaway
At no point does MyInt behave like a separate custom type. It is only an alias.
If you changed using MyInt = int; to typedef int MyInt;, the program would behave the same way.
Real World Use Cases
Simplifying long template types
Large C++ codebases often use long type names:
using StringMap = std::unordered_map<std::string, std::vector<int>>;
This makes declarations shorter and easier to understand.
Improving readability
Aliases can express intent:
using UserName = std::string;
using FilePath = std::string;
Even though both are still std::string, the names help explain what the value represents.
Hiding implementation details
A project may choose one implementation now and change it later:
using IdList = std::vector<int>;
If the project later changes to std::deque<int>, fewer files need updates.
Template convenience
Modern C++ libraries often use alias templates:
template<class T>
using Ptr = std::shared_ptr<T>;
This reduces repetition in APIs and internal code.
Function pointer and callback aliases
Callbacks can be hard to read without aliases:
using Callback = void (*)(int, bool);
This improves clarity when callbacks appear in multiple places.
Real Codebase Usage
In real C++ projects, developers usually prefer using for new code.
Common patterns
Naming complex types once
using Headers = std::map<std::string, std::string>;
This keeps function signatures readable.
Alias templates for reusable abstractions
template<class T>
using ResultList = std::vector<T>;
This is common in generic libraries and application frameworks.
Configuration-dependent types
A project may switch implementations behind one alias:
using Buffer = std::vector<char>;
Later, the project can replace it with another container if needed.
Error-handling and callback types
using ErrorHandler = std::function<void(const std::string&)>;
This makes interfaces much easier to read.
Style guidance in modern code
Many teams follow this rule:
- use
usingin modern C++ - keep
typedefmainly for older code or legacy style consistency
Important limitation
Aliases improve readability, but they do not enforce stronger type safety.
For example:
using Meters = double;
using Seconds = double;
These are still both double, so they can be mixed accidentally. If you need protection against that, create real wrapper types.
Common Mistakes
Mistake 1: Thinking an alias creates a new type
Broken assumption:
using UserId = int;
using ProductId = int;
void printUser(UserId id) {}
int main() {
ProductId p = 10;
printUser(p); // This is allowed
}
Why it happens:
UserIdandProductIdare both justint- aliases do not provide type safety
How to avoid it:
- use
structorclasswhen you need a distinct type
struct UserId {
int value;
};
Mistake 2: Using typedef for complex templates and getting confused
Harder-to-read code:
typedef std::vector<std::pair<int, int>> PairList;
Better:
using PairList = std::vector<std::pair<int, int>>;
How to avoid it:
- prefer
usingin modern C++ for readability
Mistake 3: Expecting typedef to support alias templates
Invalid idea:
// typedef cannot do this directly
template<class T>
typedef std::vector<T> Vec;
Use using instead:
template<class T>
using Vec = std::vector<T>;
Mistake 4: Using aliases instead of real abstractions
Aliases can improve names, but they do not add behavior.
using Temperature = double;
This does not let you attach validation, methods, or invariants.
How to avoid it:
- use a class or struct when the concept has rules or behavior
Mistake 5: Assuming using changes conversion rules
using Age = int;
Age age = 20;
double d = age; // allowed, just like int to double
Why:
- conversion behavior is the same as the original type
usingdoes not alter type semantics
Comparisons
| Feature | typedef | using |
|---|---|---|
| Creates a type alias | Yes | Yes |
| Creates a new type | No | No |
| Works for simple aliases | Yes | Yes |
| Supports alias templates | No | Yes |
| Easier to read with complex types | Usually no | Usually yes |
| Common in legacy code | Yes | Yes |
| Preferred in modern C++ | Less often | Yes |
typedef vs using in simple cases
These are equivalent:
typedef int MyInt;
using MyInt = int;
Alias vs real type
| Example | New type? | Notes |
|---|---|---|
using Age = int; | No | Just another name for int |
typedef int Age; | No | Same as above |
struct Age { int value; }; | Yes | Distinct type |
When to choose which
- Choose
usingfor modern C++ code - Use
typedefwhen working in old codebases that already use it heavily - Use
structorclassif you need a truly distinct type
Cheat Sheet
// Old syntax
typedef int MyInt;
// Modern syntax
using MyInt = int;
Rules
- Both create a type alias
- Neither creates a new type
- Aliases follow the original type's conversion rules
usingis usually easier to readusingsupports alias templatestypedefdoes not directly support alias templates
Alias template syntax
template<class T>
using Vec = std::vector<T>;
Function pointer example
typedef void (*Handler)(int);
using Handler = void (*)(int);
If you need a real new type
struct UserId {
int value;
};
Quick decision guide
- Need a simple alias in modern C++? Use
using - Need an alias template? Use
using - Maintaining old code? You may still see
typedef - Need type safety? Use
structorclass
FAQ
Is using exactly the same as typedef in C++?
For ordinary type aliases, yes in behavior. Both create another name for an existing type and do not create a new type.
Does using create a new type in C++?
No. using creates a type alias, not a distinct type.
Why is using preferred over typedef?
using is easier to read, especially with complex types, and it supports alias templates.
Can typedef do everything using can do?
No. The main missing feature is alias templates.
Are conversions different between typedef and using?
No. Since both are aliases, conversions behave exactly like the original type.
When should I still use typedef?
Mostly when working in legacy codebases or when matching an existing style.
How do I create a truly new type in C++?
Use struct, class, or another real type definition, not a type alias.
Is using only for type aliases?
No. In C++, using also appears in other contexts, such as bringing names into scope. But in this topic, it is specifically used for type aliases.
Mini Project
Description
Create a small C++ program that defines several type aliases and demonstrates the difference between an alias and a real custom type. This project helps you see that using improves readability, but does not add type safety on its own.
Goal
Build a program that uses using for readable aliases, uses an alias template, and shows why a struct is needed for a truly separate type.
Requirements
- Define at least two simple aliases with
using - Create one alias template using a standard container
- Show that two aliases of the same base type can be used interchangeably
- Define one
structthat represents a real distinct type - Print output that demonstrates the behavior clearly
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.