When working with C++ templates, you have probably seen typename
and class
used interchangeably. Is there a difference between them? This post will explain when these two keywords can be used in templates.
Let’s consider the following example:
template <class T> class foo {}; template <typename T> class foo {};
In this context, when declaring a type template parameter, there is no difference, they are interchangeable. They can also be mixed together, as in the following example:
template<class A, typename B> struct foo { A a; B b; };
Here is what the C++ standard has to say about it (§13.2.2):
There is no semantic difference between class and typename in a type-parameter-key.
So which one should be used then? It’s all a matter of style. In fact, the standard itself is using both of them in what seems to be a completely random style, which I believe actually depended on the preferred style of the people that wrote the proposals that made it to the standard.
You can find here links to various version of the C++ standard if you want to have a look.
A recent poll I run on twitter with more than 100 respondents showed that 9 out of 10 people prefer to use typename instead of class. I personally prefer the same.
However, there are cases when these two cannot be used interchangeably. One case is dependent types, which are names that are dependent on a template parameter. Here is an example:
template <typename T> struct wrapper { using value_type = T; value_type value; }; template <typename T> struct foo { T wrapped_value; typename T::value_type get_wrapped_value() { return wrapped_value.value; } }; int main() { foo<wrapper<int>> f{ {42} }; std::cout << f.get_wrapped_value() << '\n'; }
In this snippet, foo
is a class that contains an object of a type that wraps another value and contains a public data member called value
. wrapper
is such a type. However, foo
has a method that returns the wrapped value. That is T::value_type
. However, if you use it without the typename
keyword, as seen in the snippet, you get a compiler error. The following is from the VC++ compiler:
warning C4346: 'value_type': dependent name is not a type message : prefix with 'typename' to indicate a type error C2061: syntax error: identifier 'value_type'
This is where you must use typename
but where class
is not allowed.
The following is an alternative solution where an alias is introduced in the foo
class template, which, of course, requires the typename
keyword.
template <typename T> struct foo { using wrapped_value_type = typename T::value_type; T wrapped_value; wrapped_value_type get_wrapped_value() { return wrapped_value.value; } };
As a parenthesis, there is another alternative solution to this particular problem from this example (given than we only need the wrapped value type for the return type of a function). That is the use of auto for the return type.
template <typename T> struct foo { T wrapped_value; auto get_wrapped_value() { return wrapped_value.value; } };
Prior to C++17, there was another case where these two could not be used interchangeably. It is the case of template template parameters, where class had to be used. Let’s look at an example.
First, consider there is another class template that has two type parameters, as shown in the following snippet.
template <typename T, typename U> struct dual_wrapper { using value_type1 = T; using value_type2 = U; value_type1 value; value_type2 another_value; };
Having the foo
class template from the previous example, we could write the following:
foo<wrapper<int>> f{ {42} }; std::cout << f.get_wrapped_value() << '\n'; foo<dual_wrapper<int, double>> f2{ {43, 15.0} }; std::cout << f2.get_wrapped_value() << '\n';
However, what if you want to restrict the instantiation to wrappers that have a single type parameter? Then, you can modify the foo
class template as follows:
template <typename V, template <typename> class T> struct foo { T<V> wrapped_value; auto get_wrapped_value() { return wrapped_value.value; } };
The template <typename> class T
part is a template template parameter. It used to require the keyword class
but as of C++17, typename
can be used here to, as in template <typename> typename T
.
We need to change a bit the way objects of type foo
are declared. However, attempting to use dual_wrapper
now results in a compiler error.
foo<int, wrapper> f{ {42} }; std::cout << f.get_wrapped_value() << '\n'; foo<int, dual_wrapper> f2{ {43, 15.0} }; // error
Can anything else be used in place of a the typename
or class
keywords? As of C++20, when declaring a type template parameter, the answer is yes. A concept name can be used instead. Here is an example:
template <typename T> concept Numeric = std::is_arithmetic_v<T>; template <Numeric T> struct wrapper { T value; }; wrapper<int> vi{ 42 }; wrapper<std::string> vs{ "42"s }; // error: 'wrapper': the associated constraints are not satisfied
In this snippet, Numeric
is a concept used to ensure that the wrapper
class template can only be instantiated with numerical types, such as int
or double
. The type template parameter has the form Numeric T
instead of class T
or typename T
.
There is an alternative syntax to declaring the wrapper class template with the same semantics. This is shown below:
template <typename T> requires Numeric<T> struct wrapper { T value; };
We have discussed so far type template parameters and template template parameters. However, there is a 3rd category of template parameters, non-type template parameters. These ones are introduced not with typename
, class
, or the name of a concept, but with the name of a structural type which can be a lvalue reference types, an integral type, a pointer type, a pointer to member type, an enumeration type, std::nullptr_t
, and, as of C++20, a floating-point type, or a literal class type that satisfies some conditions. Here are some examples:
template <typename T, size_t Size> struct fixed_size_array { T[Size] values; }; fixed_size_array<int, 4> arr;
The placeholder auto
can be used instead of the actual type, with the forms auto
, auto**
, auto&
and decltype(auto)
.
template <auto V> struct foo { decltype(V) const value = V; }; foo<42> f1; std::cout << f1.value << '\n'; foo<42.0> f2; std::cout << f2.value << '\n';
To summarize all this:
- when declaring type template parameters, use either
typename
orclass
, or the name of a concept - when declaring template template parameters, use either
typename
orclass
if you’re using at least C++17, or class for a previous standard version - when declaring non-type template parameters, use the name of a structural type, or the placeholder
auto
ordecltype(auto)
- when declaring dependent types use
typename