Last Update:
Simplify template code with fewer typename in C++20
C++ not only grows with cool features but also improves and makes code simpler and readable. It’s evident in template code. For example, typename
inside dependent names is now much easier (not) to use.
If you have an expression like X<T>::name
, should you always put typename
in front?
See the full details below.
Implementing an iterator for a container
A few weeks ago, I experimented with a code kata and implemented a container and an iterator for “vectors of vectors.” I set the C++20 flag in Compiler Explorer wrote the code. But then I tried the C++17 flag, and I was surprised to see how much I had to add to compile it!
To simplify things, let’s look at a simple wrapper for a container class. It uses std::vector
as internal storage and only exposes some essential functionality.
template <typename T>
class MyVec {
public:
MyVec() = default;
MyVec(const std::vector<T>& vec) : data_(vec) { }
size_t size() const { return data_.size(); }
// ...
private:
std::vector<T> data_; // storage
};
So far, it’s elementary… and you may ask what’s the point.
But have a look at the declaration of a nested class, iterator
.
// C++17 mode
struct Iterator {
using iterator_category = typename vector<T>::iterator::iterator_category;
using difference_type = typename vector<T>::iterator::difference_type;
using value_type = typename vector<T>::iterator::value_type;
using pointer = typename vector<T>::iterator::pointer;
using reference = typename vector<T>::iterator::reference;
Iterator(typename std::vector<T>::iterator it,
typename std::vector<T>::iterator realEnd) noexcept
: it_(it)
, end_(realEnd)
{
}
// some implementation...
private:
typename std::vector<T>::iterator end_;
typename std::vector<T>::iterator it_;
};
And now, with this “amazing” container, we can write and run the following code:
int main() {
MyVec<int> vec { {1, 2, 3, 4} };
for (auto& elem : vec)
std::cout << elem << ", ";
}
See here @Compiler Explorer
As you can see, the whole iterator is very simple, but due to the nature of nested type and dependant names, we need to use a lot of typename
in C++17 mode.
Why is it needed?
Let’s review some core concepts.
The Basics
From the proposal P0634 - Down with typename
!:
If
X<T>::Y
— whereT
is a template parameter — is to denote a type, it must be preceded by the keywordtypename
; otherwise, it is assumed to denote a name producing an expression
Before C++20, we had two exceptions to this rule (specifying a base class and member initializer ids).
This rule was mainly to help the compiler. For example:
struct One {
using X = std::pair<double, double>;
using Y = int;
static constexpr int val = 0;
};
template <typename T>
struct Test : T::X { // typename not required
int d {T::val}; // typename not required
typename T::Y test; // typename required
};
Test<One> t;
However, the compiler vendors quickly realized that they knew if the syntax points to a type or not in many places, and finally, P0634 was added into C++20. It was one of its earliest features.
C++20 Improvements
Since C++20, we can skip many places where we declare a type, so typename
is implicit.
For example in using
:
struct Iterator {
using iterator_category = std::vector<T>::iterator::iterator_category;
using difference_type = std::vector<T>::iterator::difference_type;
using value_type = std::vector<T>::iterator::value_type;
using pointer = std::vector<T>::iterator::pointer;
using reference = std::vector<T>::iterator::reference;
Or data members:
private:
std::vector<T>::iterator end_;
std::vector<T>::iterator it_;
};
Or function parameters:
Iterator(std::vector<T>::iterator it,
std::vector<T>::iterator realEnd) noexcept
: it_(it)
, end_(realEnd)
{ }
See the updated version @Compiler Explorer
Additionally the typename
keyword is not needed in cases like:
- function declaration or a function definition
- parameters in a function or a lambda (unless that parameter-declaration appears in a default argument)
- trailing return type
- default argument of a type-parameter of a template
- type-id of a
static_cast
,cont_cast
,reinterpret_cast
ordynamic_cast
Where is it needed then?
Here’s an example from Nicolai Josuttis from his book on C++20 (published through Twitter see here ) which shows all typename
options:
See @Compiler Explorer.
Summary
Reducing the number of typename
keywords in code is a good enhancement to the language. It makes it shorter and also easier to read. When we declare a type based on a dependent template name, it could be confusing why the compiler warned about not having typename
added.
This feature is so far implemented in GCC 9.0 and MSVC VS 2019 16.10.
Thanks to a comment from cpp_learner you can see that there’s a patch in Clang waiting for review since 2018 for that feature :) ⚙D53847 C++2a P0634r3: Down with typename!.
You can also read this cool blog post by Down with typename - Shafik Yaghmour’s Blog, and for full description and rules you can see the book on C++20 by Nicolai Josuttis: C++20 - The Complete Guide. Plus there’s C++ Templates: The Complete Guide (2nd Edition) by David Vandevoorde, Nicolai M. Josuttis, Douglas Gregor.
Bonus: If you look in code, you’ll also see that in C++20, I only had to implement operator==
for the iterator. There’s no need for !=
as the C++20 compiler can write it for us! That’s a topic for another story :)
And if you want the full story of the container and an iterator for a vector of vectors, see those two exclusive articles at Patreon: part one and part two.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: