typedef vs using in C++: complete guide for the modern developer
\n\nHave you ever wondered why some C++ developers write typedef while others write using? It seems like both do the same thing — create aliases for types. But is that really the case? If you want to write clean, modern code and avoid the pitfalls of outdated constructs, this article is for you.
We'll figure out how typedef and using differ in C++, when to use which, and why the C++ community is increasingly leaning towards using. Ready? Let's go!
A brief history: from C to C++11
\n\ntypedef came to us from the C language. It's the classic way to declare a type alias. It looks like this:
\n\ntypedef unsigned long long ULL;\ntypedef std::vector<std::string> StringVector;\ntypedef void (*FunctionPtr)(int, double);\n\nThe syntax is, to put it mildly, not the most intuitive. This is especially noticeable with function pointers — even experienced developers sometimes stumble when reading such declarations.
\n\nIn C++11, a new construct appeared — using (or alias declaration). It looks much more natural:
\n\nusing ULL = unsigned long long;\nusing StringVector = std::vector<std::string>;\nusing FunctionPtr = void (*)(int, double);\n\nIt's immediately clear: on the left — the name, on the right — the type. It reads like an "assignment," which is intuitively understandable to any programmer.
\n\nThe main difference: working with templates
\n\nThis is where the most important distinction between typedef and using lies. using supports alias templates, while typedef does not.
Let's look at an example. Suppose you want to create an alias for std::unique_ptr with a custom deleter:
// With using — easy and elegant\ntemplate <typename T>\nusing CustomPtr = std::unique_ptr<T, void(*)(T*)>;\n\n// With typedef — impossible directly!\n// You have to create wrappers via structs\ntemplate <typename T>\nstruct CustomPtrOld {\n typedef std::unique_ptr<T, void(*)(T*)> type;\n};\n\n// Usage:\nCustomPtr<int> ptr1; // using — simple\nCustomPtrOld<int>::type ptr2; // typedef — cumbersome\n\nCan you feel the difference? using makes the code cleaner and shorter. And if you need to use such an alias inside a template class or function, the difference becomes even more noticeable:
template <typename T>\nclass Container {\npublic:\n // With using\n using Ptr = CustomPtr<T>;\n \n // With typedef — need typename and ::type\n typedef typename CustomPtrOld<T>::type OldPtr;\n \nprivate:\n Ptr data;\n OldPtr oldData;\n};\n\nCode readability: an obvious winner
\n\nEven for simple types, using wins in readability. Compare:
// With typedef — unclear where the name is and where the type is\ntypedef std::map<std::string, std::vector<int>> StringToIntVectorMap;\n\n// With using — everything is obvious\nusing StringToIntVectorMap = std::map<std::string, std::vector<int>>;\n\nWhen you read code, your eye naturally scans the construct from left to right. using follows this principle: first the name, then the type. typedef, on the other hand, forces you to read the type first and then the name — this contradicts intuition.
Function pointers: the pain of typedef
\n\nFunction pointers are perhaps the most disliked part of C++ for many developers. And typedef makes them even more confusing:
// typedef — read it like an incantation\ntypedef void (*Callback)(int, const std::string&);\n\n// using — like a regular variable\nusing Callback = void (*)(int, const std