Template Explicit Specialization
Let’s look at a simpler template function, one that returns whether the first value is greater than the second value:
//
// templcompare.h
// Function Templates
//
// Created by Bryan Higgs on 10/11/24.
//
#ifndef templcompare_h
#define templcompare_h
template <typename T>
bool isGreater(const T &value1, const T &value2)
{
if (value1 > value2)
return true;
return false;
}
#endif /* templcompare_h *///
// templcompare_main.cpp
// Function Templates
//
// Created by Bryan Higgs on 10/11/24.
//
#include <iostream>
#include "templcompare.h"
int main(int argc, const char * argv[])
{
int i1 = 5, i2 = 2;
double d1 = 45.2, d2 = 89.3;
const char *s1 = "Frodo", *s2 = "Bilbo";
std::cout << i1 << " > " << i2 << " " << std::boolalpha
<< isGreater(i1, i2) << std::endl;
std::cout << d1 << " > " << d2 << " " << std::boolalpha
<< isGreater(d1, d2) << std::endl;
std::cout << s1 << " > " << s2 << " " << std::boolalpha
<< isGreater(s1, s2) << std::endl;
return 0;
}This produces the following output:
5 > 2 true
45.2 > 89.3 false
Frodo > Bilbo false
Program ended with exit code: 0
But “Frodo” should be greater than “Bilbo” for normal sorting!
What’s wrong?
This result is because the isGreater function is relying on the type T to support the > operator. In the case of the type const char *, the function is comparing pointer values, not the strings.
This shows that function templates cannot always provide the right solution for all types.
We need to specify that the behavior of max should be different for the type const char *.
We do this by specifying a template explicit specialization.
A Template Specialization consists of:
- The keyword template followed by <> (with nothing between the brackets)
- Followed by the template name and a bracket pair containing the template parameters for the specialization
- The function parameter list
- followed by the function body.
//
// templcompare.h
// Function Templates
//
// Created by Bryan Higgs on 10/11/24.
//
#ifndef templcompare_h
#define templcompare_h
template <typename T>
bool isGreater(const T &value1, const T &value2)
{
if (value1 > value2)
return true;
return false;
}
template<>
bool isGreater<const char*>
(const char * const &value1,
const char * const &value2)
{
if ( strcmp(value1, value2) > 0 )
return true;
return false;
}
#endif /* templcompare_h *///
// templcompare_main.cpp
// Function Templates
//
// Created by Bryan Higgs on 10/11/24.
//
#include <iostream>
#include "templcompare.h"
int main(int argc, const char * argv[])
{
int i1 = 5, i2 = 2;
double d1 = 45.2, d2 = 89.3;
const char *s1 = "Frodo", *s2 = "Bilbo";
std::cout << i1 << " > " << i2 << " " << std::boolalpha
<< isGreater(i1, i2) << std::endl;
std::cout << d1 << " > " << d2 << " " << std::boolalpha
<< isGreater(d1, d2) << std::endl;
std::cout << s1 << " > " << s2 << " " << std::boolalpha
<< isGreater(s1, s2) << std::endl;
return 0;
}Which produces the following output:
5 > 2 true
45.2 > 89.3 false
Frodo > Bilbo true
Program ended with exit code: 0
So, now the strings (actually const char *) values are compared correctly.
Using an Ordinary Function to Achieve the Same Result
It is possible to use an ordinary (non-template) function to achieve the same result:
//
// templcompare.h
// Function Templates
//
// Created by Bryan Higgs on 10/11/24.
//
#ifndef templcompare_h
#define templcompare_h
template <typename T>
bool isGreater(const T &value1, const T &value2)
{
if (value1 > value2)
return true;
return false;
}
bool isGreater
(const char * const &value1,
const char * const &value2)
{
if ( strcmp(value1, value2) > 0 )
return true;
return false;
}
#endif /* templcompare_h *///
// templcompare_main.cpp
// Function Templates
//
// Created by Bryan Higgs on 10/11/24.
//
#include <iostream>
#include "templcompare.h"
int main(int argc, const char * argv[])
{
int i1 = 5, i2 = 2;
double d1 = 45.2, d2 = 89.3;
const char *s1 = "Frodo", *s2 = "Bilbo";
std::cout << i1 << " > " << i2 << " " << std::boolalpha
<< isGreater(i1, i2) << std::endl;
std::cout << d1 << " > " << d2 << " " << std::boolalpha
<< isGreater(d1, d2) << std::endl;
std::cout << s1 << " > " << s2 << " " << std::boolalpha
<< isGreater(s1, s2) << std::endl;
return 0;
}However, this is not a template explicit specialization.
When we define a non-template function, normal conversions are applied to the arguments.
Then we specialize a template, conversions are not applied to the argument types.
In a call to a specialized version of a template, the argument types in the call must match the specialized version function parameter types exactly. If they do not, then the compiler will instantiate an instantiation for the arguments from the template definition.
Template Argument Deduction
When a function template is called, the types and values of the template arguments are determined by looking at the types of the function arguments. This is called template argument deduction.
There are times when it is not possible for the compiler to deduce the proper template arguments for a function template:
//
// main.cpp
// Template Argument Deduction
//
// Created by Bryan Higgs on 10/13/24.
//
#include <iostream>
template <typename T>
T *create()
{ // Make a T and return a pointer to it
return new T;
}
int main(int argc, const char * argv[])
{
int *p = create(); // error: could not deduce
// template argument for 'T'
return 0;
}… produces the following compile-time error:
main.cpp:18:12 No matching function for call to 'create'
Candidate template ignored: couldn't infer template argument 'T'
Explicit Template Specification
The solution to this is to use explicit template specification:
//
// main.cpp
// Template Argument Deduction
//
// Created by Bryan Higgs on 10/13/24.
//
#include <iostream>
template <typename T>
T *create()
{ // Make a T and return a pointer to it
return new T;
}
int main(int argc, const char * argv[])
{
int *p = create<int>();
return 0;
}This now compiles.
One common use of explicit specification is to provide a return type for a function template:
//
// ReturnType.cpp
// Template Argument Deduction
//
// Created by Bryan Higgs on 10/13/24.
//
#include <iostream>
template <typename T, typename U>
U implicit_cast(U u)
{
std::cout << "T:" << typeid(T).name() << " "
<< "U:" << typeid(U).name() << std::endl;
return u;
}
int main(int argc, const char * argv[])
{
int ivalue = 5;
double dvalue = implicit_cast(ivalue);
// error: can't deduce T
dvalue = implicit_cast<double>(ivalue);
// T is double; U is int
char cvalue = implicit_cast<char, double>(ivalue);
// T is char; U is double
char *cptr = implicit_cast<char *, int>(ivalue);
// T is char *; U is int
// error can't convert int to char*
return 0;
}which produces the following compile-time errors:
ReturnType.cpp:21:19 No matching function for call to 'implicit_cast'
Candidate template ignored: couldn't infer template argument 'T'
ReturnType.cpp:27:9 Cannot initialize a variable of type 'char *' with an rvalue of type 'int'
When we remove the offending statements:
//
// ReturnType.cpp
// Template Argument Deduction
//
// Created by Bryan Higgs on 10/13/24.
//
#include <iostream>
template <typename T, typename U>
U implicit_cast(U u)
{
std::cout << "T:" << typeid(T).name() << " "
<< "U:" << typeid(U).name() << std::endl;
return u;
}
int main(int argc, const char * argv[])
{
int ivalue = 5;
double dvalue = implicit_cast<double>(ivalue);
// T is double; U is int
char cvalue = implicit_cast<char, double>(ivalue);
// T is char; U is double
return 0;
}the program now compiles, runs, and produces the following output:
T:d U:i
T:c U:d
Program ended with exit code: 0
Translation:
Tis of typedouble;Uis of typeintTis of typechar;Uis of typedouble
Name Mangling
Here I’m using typeid.name() to output the type of the variable. Unfortunately, the output of this function is implementation-dependent (see here). The output for this program, which used the gcc C++ compiler, is in ‘mangled’ format.
Here’s a breakdown of how types are represented in mangled names:
Primitive Types:
- void:
v- int:
i- char:
c- short:
s- long:
l- long long:
x- unsigned int:
Ui- unsigned char:
Uc- unsigned short:
Us- unsigned long:
Ul- unsigned long long:
Ux- float:
f- double:
d- bool:
b- wchar_t:
w