function
Wrapper and Template InefficienciesConsider the following line of code:
answer = ef(q);
What is ef
? It could be the name of a function. It could be a pointer to a function. It could be a function object. It could be a name assigned to a lambda expression. These all are examples of callable types. The abundance of callable types can lead to template inefficiencies. To see this, let’s examine a simple case.
First, let’s define some templates in a header file, as shown in Listing 18.6.
// somedefs.h
#include <iostream>
template <typename T, typename F>
T use_f(T v, F f)
{
static int count = 0;
count++;
std::cout << " use_f count = " << count
<< ", &count = " << &count << std::endl;
return f(v);
}
class Fp
{
private:
double z_;
public:
Fp(double z = 1.0) : z_(z) {}
double operator()(double p) { return z_*p; }
};
class Fq
{
private:
double z_;
public:
Fq(double z = 1.0) : z_(z) {}
double operator()(double q) { return z_+ q; }
};
The use_f()
template uses the parameter f to represent a callable type:
return f(v);
Next the program in Listing 18.7 calls the use_f()
template function six times.
// callable.cpp -- callable types and templates
#include "somedefs.h"
#include <iostream>
double dub(double x) {return 2.0*x;}
double square(double x) {return x*x;}
int main()
{
using std::cout;
using std::endl;
double y = 1.21;
cout << "Function pointer dub:
";
cout << " " << use_f(y, dub) << endl;
cout << "Function pointer square:
";
cout << " " << use_f(y, square) << endl;
cout << "Function object Fp:
";
cout << " " << use_f(y, Fp(5.0)) << endl;
cout << "Function object Fq:
";
cout << " " << use_f(y, Fq(5.0)) << endl;
cout << "Lambda expression 1:
";
cout << " " << use_f(y, [](double u) {return u*u;}) << endl;
cout << "Lambda expression 2:
";
cout << " " << use_f(y, [](double u) {return u+u/2.0;}) << endl;
return 0;
}
The template parameter T
is set to type double
for each call. What about template parameter F
? Each time the actual argument is something that takes a type double
argument and returns a type double
value, so it might seem that F
would be the same type for all six calls to use_f()
and that the template would be instantiated just once. But as the following sample output shows, that belief is naïve:
Function pointer dub:
use_f count = 1, &count = 0x402028
2.42
Function pointer square:
use_f count = 2, &count = 0x402028
1.1
Function object Fp:
use_f count = 1, &count = 0x402020
6.05
Function object Fq:
use_f count = 1, &count = 0x402024
6.21
Lambda expression 1:
use_f count = 1, &count = 0x405020
1.4641
Lambda expression 2:
use_f count = 1, &count = 0x40501c
1.815
The template function use_f()
has a static member count
, and we can use its address to see how many instantiations are made. There are five distinct addresses, so there must have been five distinct instantiations of the use_f()
template.
To see what is happening, consider how the compiler determines the type for the F
template parameter. First, look at this call:
use_f(y, dub);
Here dub
is the name of a function that takes a double
argument and returns a double
value. The name of a function is a pointer, hence the parameter F
becomes type double (*)(double)
, a pointer to a function with a double
argument and a double
return value.
The next call is this:
use_f(y, square);
Again, the second argument is type double (*)(double)
, so this call uses the same instantiation of use_f()
as the first call.
The next two calls to use_f()
have objects as second arguments, so F
becomes type Fp
and Fq
respectively, so we get two new instantiations for these values of F
. Finally, the last two calls set F
to whatever types the compiler uses for lambda expressions.
18.225.57.164