header image
Pointer and Non-Pointer Overloading For Template Functions
February 10th, 2012 under Programming. [ Comments: none ]

Yesterday, I run into the problem that function templates cannot be partially specialized. My goal was to distinguish between pointers and non-pointers.

So trying something like

1
2
3
4
5
6
7
8
9
10
template<typename T> 
void foo(T t) { printf("T\n"); }
 
// Variant 1
template<typename T> 
void foo<T*>(T t) { printf("T*\n"); }
 
// Variant 2
template<typename T> 
void foo<T*>(T* t) { printf("T*\n"); }
template<typename T> 
void foo(T t) { printf("T\n"); }

// Variant 1
template<typename T> 
void foo<T*>(T t) { printf("T*\n"); }

// Variant 2
template<typename T> 
void foo<T*>(T* t) { printf("T*\n"); }

always resulted in a compile error. To resolve this problem we can rely on the good-old function overloading mechanism. So

1
2
3
4
5
template<typename T> 
void foo(T t) { printf("T\n"); }
 
template<typename T> 
void foo(T* t) { printf("T*\n"); }
template<typename T> 
void foo(T t) { printf("T\n"); }

template<typename T> 
void foo(T* t) { printf("T*\n"); }

works as expected. However now in the non-pointer case always creates a copy, which isn’t desirable. Thus i experimented with call-by-reference. Everything is straight forward when no const qualifiers are in play but the const-reference case has a pitfall. Let’s have a look at this pitfall.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T> 
void foo(const T& t) { printf("T&\n"); }
 
template<typename T> 
void foo(const T*& t) { printf("T*\n"); }
 
int main(void)
{
  int i = 0;
  int* pi = &i;
  const int* cpi = &i;
  foo(i);        // (a) prints T&
  foo(pi);       // (b) prints T&  
  foo(cpi);      // (c) prints T*
  return 0;
}
template<typename T> 
void foo(const T& t) { printf("T&\n"); }

template<typename T> 
void foo(const T*& t) { printf("T*\n"); }

int main(void)
{
  int i = 0;
  int* pi = &i;
  const int* cpi = &i;
  foo(i);        // (a) prints T&
  foo(pi);       // (b) prints T&  
  foo(cpi);      // (c) prints T*
  return 0;
}

As indicated, this sample doesn’t produce the expected results. The pitfall lays in the const qualifier. We need to remember that

1
const T*
const T*
is equal to
1
T const*
T const*
. New let’s have a look at the type
1
const T*&
const T*&
, which is equal to
1
T const* &
T const* &
. This is a reference to a variable pointer but the pointer points to a constant object. So we could change the address of the pointer from within the function (which isn’t desirable). The call (b) passes a variable of type which the compiler tries to match to the two template functions. So the type inference for the first function is
1
T = int*
T = int*
which matches. The second function on the other hand doesn’t match at all because, as we saw earlier, the second function expects a pointer to a const object i.e.
1
const int*
const int*
or
1
int const*
int const*
. The call (c) prints T* because it is of this type and is as such a better match for the second function than for the first function. Eventually, we see that what we got is reasonable but not what we wanted.

To determine what we need to write lets look again at the inferred type for call (b) and the first function. We saw that

1
T = int*
T = int*
. To see what we need to write we can simply replace T with the inferred pointer type. Well, hang on that’s what we wrote isn’t it. It’s
1
const T&
const T&
=>
1
const int*&
const int*&
and so we deduce that
1
const T*&
const T*&
should written. Yet, we saw that this isn’t producing what we want. The pitfall is in the place of the const in the first-place. We know that
1
const T&
const T&
is the same as
1
T const&
T const&
. If we execute the same substitution as before we get
1
int* const&
int* const&
leading to
1
T* const&
T* const&
. When we test this we get what we want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T> 
void foo(T const& t) { printf("T&\n"); }
 
template<typename T> 
void foo(T* const& t) { printf("T*\n"); }
 
int main(void)
{
  int i = 0;
  int* pi = &i;
  foo(i);        // prints T&
  foo(pi);       // prints T*
  foo(cpi);      // (c) prints T*
  return 0;
}
template<typename T> 
void foo(T const& t) { printf("T&\n"); }

template<typename T> 
void foo(T* const& t) { printf("T*\n"); }

int main(void)
{
  int i = 0;
  int* pi = &i;
  foo(i);        // prints T&
  foo(pi);       // prints T*
  foo(cpi);      // (c) prints T*
  return 0;
}

The problem was that I forgot the realize that the const in

1
const T&
const T&
is “bound” to the reference. Well, it’s a stupid mistake when you think about it but it needed some thinking 😛 It might be a good idea to get used to writing
1
T const&
T const&
instead of
1
const T&
const T&
.