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
is equal to
. New let’s have a look at the type
, which is equal to
. 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
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.
or
. 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
. 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
=>
and so we deduce that
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
is the same as
. If we execute the same substitution as before we get
leading to
. 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
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
instead of
.
|