The choice between typename and class

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 or class, or the name of a concept
  • when declaring template template parameters, use either typename or class 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 or decltype(auto)
  • when declaring dependent types use typename

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.