Discussion: A Constant Struggle

Execute the code to understand the output and gain insights into template argument deduction.

Run the code

Now, it’s time to execute the code and observe the output.

Press + to interact
#include <iostream>
#include <type_traits>
template <typename T>
void byValue(T t)
{
std::cout << std::is_const_v<T>; // true if T is const
}
template <typename T>
void byReference(T &t)
{
std::cout << std::is_const_v<T>; // true if T is const
}
int main()
{
int nonConstInt = 0;
const int constInt = 0;
byValue(nonConstInt);
byValue(constInt);
byReference(nonConstInt);
byReference(constInt);
}

Understanding the output

The two function templates byValue and byReference take their parameters by value and by reference, respectively. We then call these two function templates with a non-const and a const int as arguments. In which cases is T deduced as const?

Template parameter

Before we start digging into the deduction rules, let’s clarify two concepts that can be easily mixed up. The T in template <typename T> is called the template parameter. The goal of template argument deduction is to find T. On the other hand, the T and T & in the function signatures byValue(T t) and byReference(T &t) are called the template parameter types. These are the actual parameter types of the function templates and are what we use for deduction.

Let’s refer to these template parameter types as P to distinguish them from the template parameters T. Here are three examples:

Template Parameter

Template Parameter Type P

template <typename T>

void byValue(T t)

T

T

template <typename T>

void byReference(T &t)

T

T &

template <typename ValueType>

void f(const std::vector<ValueType>& v)

ValueType

const std::vector<ValueType>&

As we can see from the examples, in both byValue and byReference, the template parameter is T. In byValue, the template parameter type P is also just T, but in byReference, it is T &.

The byValue call

With that out of the way, let’s dig into the deduction rules. First, let’s look at the calls to byValue:

template <typename T>
void byValue(T t);
int nonConstInt = 0;
const int constInt = 0;
byValue(nonConstInt);
byValue(constInt);

Remember, the template parameter is T, and the template parameter type P is also just T (as opposed to, for example, T &).

When deducing the template parameter type P, if it’s not a reference type, the deduction rules say to ignore any top-level cv-qualifiers (const or volatile) of the argument. Since P is just T, it’s not a reference type, and cv-qualifiers are ignored.

Wait, no matter if we pass a non-const or a const argument, we pretend it’s non-const! Isn’t that a bit shady? It’s not. When P is not a reference type, it means we pass the argument by value. It doesn’t matter whether the original argument was const, the t parameter inside byValue is a copy, and we can’t modify the original argument.

Since top-level cv-qualifiers are ignored, we deduce P from a non-const int in both calls to byValue and find P == int in both cases. And since P == T, T is also non-const. 0 is printed for both calls.

The byReferences call

Next, let’s look at the calls to byReference:

template <typename T>
void byReference(T &t);
int nonConstInt = 0;
const int constInt = 0;
byReference(nonConstInt);
byReference(constInt);

The template parameter is T, and the template parameter type P is T &.

This time, P is a reference, and we do not ignore the top-level cv-qualifiers of the argument. In this case, that’s important! The reference parameter t will bind to our original argument, so it’s important that we don’t try, for instance, to bind a non-const reference to a const argument.

When passed the non-const nonConstInt, P is deduced as int &, which means that T is int. It’s not const, so 0 is printed.

When passed the const constInt, P is deduced as const int &, which means that T is const int. It is const, so 1 is printed.

The complete rules for template argument deduction can be a bit daunting to read, but at least this part about ignoring top-level cv-qualifiers is intuitive enough to remember it.

Auto

The deduction rules for auto are based on the rules for template argument deduction. So now that we know these template argument deduction rules, we also know the corresponding auto deduction rules!

auto t1 = nonConstInt; // non-const object
auto t2 = constInt; // non-const object
auto &t3 = nonConstInt; // reference to non-const object
auto &t4 = constInt; // reference to const object

Attempt the following question to assess your understanding.

Consider the following code:

int x = 42;
const int y = x;
int &z = x;


auto a = y;
auto &b = z;  
const auto c = z; 
1

What is the type of a?

A)

int

B)

const int

C)

int&

D)

const int&

Question 1 of 30 attempted

Level up your interview prep. Join Educative to access 70+ hands-on prep courses.