The function Wrapper and Template Inefficiencies

Consider 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.

Listing 18.6. somedefs.h


// 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.

Listing 18.7. callable.cpp


// 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.57.164