C++ got me again recently.

It’s easy to make a mistake with template deduction, especially when you confuse it with function overloads.

I wanted to write one behaviour for pointers and one for everything else. So I quickly churned out the following:

template <typename T>
int bar(T const& a) { /* ... */ }
template <typename T>
int bar(T const* a) { /* ... */ }

Pop quiz: Given the following snippet, what is the expected output?

#include <iostream>

char const* foo(int const& a) { return "int const&"; }
char const* foo(int const* a) { return "int const*"; }

template <typename T>
char const* bar(T const& a) { return "int const&"; }
template <typename T>
char const* bar(T const* a) { return "int const*"; }

int main() {
    int n = 2;
    std::cout << "foo: " << foo(&n) << "\n";
    std::cout << "bar: " << bar(&n) << "\n";
}

If you guessed both calls use the pointer overload, then it’s a good thing you’re reading this!

Output:

foo: int const*
bar: int const&

Unintutive Template Deduction

C++ template deduction has rules for template overloading. Specifically, when given two templates the compiler tries to find the one with the most narrow type constraints. So why, in this case, does it pick the reference over the pointer when being given a pointer?

template <typename T>
int bar(T const& a) { /* ... */ }  // Instantiated template
template <typename T>
int bar(T const* a) { /* ... */ }

int main() {
    int n = 2;
    std::cout << "bar: " << bar(&n) << "\n";
}

… Whoops.

T deduces to int*. So the options are:

int bar(int* const& a) { /* ... */ }  // (a): Instantiated template
int bar(int* const* a) { /* ... */ }  // (b)

Of course it picks the reference. &n is not a pointer pointer!

The fix is to move the pointer to the other side of the const. This results in T = int* in (a) and T = int in (b). (b) results in a less specific deduced type, meaning it is a more specific overload and therefore picked by the compiler1. Fixed :).

template <typename T>
// Original:
// int bar(T const* a)
int bar(T* const a) { /* ... */ }

See for yourself.

  1. Talking about templates almost requires confusing language… I tried my best.