Template Function Evolution

In the early days of C++, most people didn’t envision how powerful and useful template functions and template classes would prove to be. (Probably they didn’t even expend their envisionary powers on the topic.) But clever and dedicated programmers pushed the limits of template techniques and expanded the ideas of what was possible. Feedback from those who developed familiarity with templates led to changes that were incorporated into the C++98 Standard as well as the addition of the Standard Template Library. Since then, template programmers have continued to explore the possibilities offered by the genre, and occasionally they bump up against limitations. Their feedback has led to some changes in the C++11 Standard. We’ll look at a couple of related problems now and their solutions.

What’s That Type?

One problem is that when you write a template function, it’s not always possible in C++98 to know what type to use in a declaration. Consider this partial example:

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    ?type? xpy = x + y;
    ...
}

What should the type for xpy be? We don’t know in advance how ft() might be used. The proper type might be T1 or T2 or some other type altogether. For example, T1 could be double and T2 could be int, in which case the type of the sum is double. Or T1 could be short and T2 could be int, in which case the type of the sum is int. Or suppose T1 is short and T2 is char. Then addition invokes automatic integer promotions, and the resultant type is int. Also the + operator can be overloaded for structures and classes, complicating the options further. Therefore, in C++98 there is no obvious choice for the type of xpy.

The decltype Keyword (C++11)

The C++11 solution is a new keyword: decltype. It can be used in this way:

int x;
decltype(x) y;   // make y the same type as x

The argument to decltype can be an expression, so in the ft() example, we could use this code:

decltype(x + y) xpy;  // make xpy the same type as x + y
xpy = x + y;

Alternatively, we could combine these two statements into an initialization:

decltype(x + y) xpy = x + y;

So we can fix the ft() template this way:

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    decltype(x + y) xpy = x + y;
    ...
}

The decltype facility is a bit more complex than it might appear from these examples. The compiler has to go through a checklist to decide on the type. Suppose we have the following:

decltype(expression) var;

Here’s a somewhat simplified version of the list.

Stage 1: If expression is an unparenthesized identifier (that is, no additional parentheses), then var is of the same type as the identifier, including qualifiers such as const:

double x = 5.5;
double y = 7.9;
double &rx = x;
const double * pd;
decltype(x) w;       // w is type double
decltype(rx) u = y;  // u is type double &
decltype(pd) v;      // v is type const double *

Stage 2: If expression is a function call, then var has the type of the function return type:

long indeed(int);
decltype (indeed(3)) m; // m is type int


Note

The call expression isn’t evaluated. In this case, the compiler examines the prototype to get the return type; there’s no need to actually call the function.


Stage 3: If expression is an lvalue, then var is a reference to the expression type. This might seem to imply that earlier examples such as w should have been reference types, given that w is an lvalue. However, keep in mind that case was already captured in Stage 1. For this stage to apply, expression can’t be an unparenthesized identifier. So what can it be? One obvious possibility is a parenthesized identifier:

double xx = 4.4;
decltype ((xx)) r2 = xx;  // r2 is double &
decltype(xx) w = xx;      // w is double (Stage 1 match)

Incidentally, parentheses don’t change the value or lvaluedness of an expression. For example, the following two statements have the same effect:

xx = 98.6;
(xx) = 98.6;  // () don't affect use of xx

Stage 4: If none of the preceding special cases apply, var is of the same type as expression:

int j = 3;
int &k = j
int &n = j;
decltype(j+6) i1;   // i1 type int
decltype(100L) i2;  // i2 type long
decltype(k+n) i3;   // i3 type int;

Note that although k and n are references, the expression k+n is not a reference; it’s just the sum of two ints, hence an int.

If you need more than one declaration, you can use typedef with decltype:

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    typedef decltype(x + y) xytype;
    xytype xpy = x + y;
    xytype arr[10];
    xytype & rxy = arr[2];   // rxy a reference
    ...
}

Alternative Function Syntax (C++11 Trailing Return Type)

The decltype mechanism by itself leaves another related problem unsolved. Consider this incomplete template function:

template<class T1, class T2>
?type? gt(T1 x, T2 y)
{
    ...
    return x + y;
}

Again, we don’t know in advance what type results from adding x and y. It might seem that we could use decltype(x + y) for the return type. Unfortunately, at that point in the code, the parameters x and y have not yet been declared, so they are not in scope (visible and usable to the compiler). The decltype specifier has to come after the parameters are declared. To make this possible, C++11 allows a new syntax for declaring and defining functions. Here’s how it works using built-in types. The prototype

double h(int x, float y);

can be written with this alternative syntax:

auto h(int x, float y) -> double;

This moves the return type to after the parameter declarations. The combination -> double is called a trailing return type. Here, auto, in another new C++11 role, is a placeholder for the type provided by the trailing return type. The same form would be used with the function definition:

auto h(int x, float y) -> double
{/* function body */};

Combining this syntax with decltype leads to the following solution for specifying the return type for gt():

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
    ...
    return x + y;
}

Now decltype comes after the parameter declarations, so x and y are in scope and can be used.

Summary

C++ has expanded C function capabilities. By using an inline keyword with a function definition and by placing that definition ahead of the first call to that function, you suggest to the C++ compiler that it make the function inline. That is, instead of having the program jump to a separate section of code to execute the function, the compiler replaces the function call with the corresponding code inline. An inline facility should be used only when the function is short.

A reference variable is a kind of disguised pointer that lets you create an alias (that is, a second name) for a variable. Reference variables are primarily used as arguments to functions that process structures and class objects. Normally, an identifier declared as a reference to a particular type can refer only to data of that type. However, when one class is derived from another, such as ofstream from ostream, a reference to the base type may also refer to the derived type.

C++ prototypes enable you to define default values for arguments. If a function call omits the corresponding argument, the program uses the default value. If the function includes an argument value, the program uses that value instead of the default. Default arguments can be provided only from right to left in the argument list. Thus, if you provide a default value for a particular argument, you must also provide default values for all arguments to the right of that argument.

A function’s signature is its argument list. You can define two functions having the same name, provided that they have different signatures. This is called function polymorphism, or function overloading. Typically, you overload functions to provide essentially the same service to different data types.

Function templates automate the process of overloading functions. You define a function by using a generic type and a particular algorithm, and the compiler generates appropriate function definitions for the particular argument types you use in a program.

Chapter Review

1. What kinds of functions are good candidates for inline status?

2. Suppose the song() function has this prototype:

void song(const char * name, int times);

a. How would you modify the prototype so that the default value for times is 1?

b. What changes would you make in the function definition?

c. Can you provide a default value of "O, My Papa" for name?

3. Write overloaded versions of iquote(), a function that displays its argument enclosed in double quotation marks. Write three versions: one for an int argument, one for a double argument, and one for a string argument.

4. The following is a structure template:

struct box
{
    char maker[40];
    float height;
    float width;
    float length;
    float volume;
};

a. Write a function that has a reference to a box structure as its formal argument and displays the value of each member.

b. Write a function that has a reference to a box structure as its formal argument and sets the volume member to the product of the other three dimensions.

5. What changes would need be made to Listing 7.15 so that the functions fill() and show() use reference parameters?

6. The following are some desired effects. Indicate whether each can be accomplished with default arguments, function overloading, both, or neither. Provide appropriate prototypes.

a. mass(density, volume) returns the mass of an object having a density of density and a volume of volume, whereas mass(density) returns the mass having a density of density and a volume of 1.0 cubic meters. All quantities are type double.

b. repeat(10, "I'm OK") displays the indicated string 10 times, and repeat("But you're kind of stupid") displays the indicated string 5 times.

c. average(3,6) returns the int average of two int arguments, and average(3.0, 6.0) returns the double average of two double values.

d. mangle("I'm glad to meet you") returns the character I or a pointer to the string "I'm mad to gleet you", depending on whether you assign the return value to a char variable or to a char* variable.

7. Write a function template that returns the larger of its two arguments.

8. Given the template of Chapter Review Question 7 and the box structure of Chapter Review Question 4, provide a template specialization that takes two box arguments and returns the one with the larger volume.

9. What types are assigned to v1, v2, v3, v4, and v5 in the following code (assuming the code is part of a complete program)?

int g(int x);
...
float m = 5.5f;
float & rm = m;
decltype(m) v1 = m;
decltype(rm) v2 = m;
decltype((m)) v3 = m;
decltype (g(100)) v4;
decltype (2.0 * m) v5;

Programming Exercises

1. Write a function that normally takes one argument, the address of a string, and prints that string once. However, if a second, type int, argument is provided and is nonzero, the function should print the string a number of times equal to the number of times that function has been called at that point. (Note that the number of times the string is printed is not equal to the value of the second argument; it is equal to the number of times the function has been called.) Yes, this is a silly function, but it makes you use some of the techniques discussed in this chapter. Use the function in a simple program that demonstrates how the function works.

2. The CandyBar structure contains three members. The first member holds the brand name of a candy bar. The second member holds the weight (which may have a fractional part) of the candy bar, and the third member holds the number of calories (an integer value) in the candy bar. Write a program that uses a function that takes as arguments a reference to CandyBar, a pointer-to-char, a double, and an int and uses the last three values to set the corresponding members of the structure. The last three arguments should have default values of “Millennium Munch,” 2.85, and 350. Also the program should use a function that takes a reference to a CandyBar as an argument and displays the contents of the structure. Use const where appropriate.

3. Write a function that takes a reference to a string object as its parameter and that converts the contents of the string to uppercase. Use the toupper() function described in Table 6.4 of Chapter 6. Write a program that uses a loop which allows you to test the function with different input. A sample run might look like this:

Enter a string (q to quit): go away
GO AWAY
Next string (q to quit): good grief!
GOOD GRIEF!
Next string (q to quit): q
Bye.

4. The following is a program skeleton:

#include <iostream>
using namespace std;
#include <cstring>      // for strlen(), strcpy()
struct stringy {
    char * str;        // points to a string
    int ct;            // length of string (not counting '')
    };

// prototypes for set(), show(), and show() go here
int main()
{
    stringy beany;
    char testing[] = "Reality isn't what it used to be.";

    set(beany, testing);    // first argument is a reference,
                // allocates space to hold copy of testing,
                // sets str member of beany to point to the
                // new block, copies testing to new block,
                // and sets ct member of beany
    show(beany);      // prints member string once
    show(beany, 2);   // prints member string twice
    testing[0] = 'D';
    testing[1] = 'u';
    show(testing);    // prints testing string once
    show(testing, 3); // prints testing string thrice
    show("Done!");
    return 0;
}

Complete this skeleton by providing the described functions and prototypes. Note that there should be two show() functions, each using default arguments. Use const arguments when appropriate. Note that set() should use new to allocate sufficient space to hold the designated string. The techniques used here are similar to those used in designing and implementing classes. (You might have to alter the header filenames and delete the using directive, depending on your compiler.)

5. Write a template function max5() that takes as its argument an array of five items of type T and returns the largest item in the array. (Because the size is fixed, it can be hard-coded into the loop instead of being passed as an argument.) Test it in a program that uses the function with an array of five int value and an array of five double values.

6. Write a template function maxn() that takes as its arguments an array of items of type T and an integer representing the number of elements in the array and that returns the largest item in the array. Test it in a program that uses the function template with an array of six int value and an array of four double values. The program should also include a specialization that takes an array of pointers-to-char as an argument and the number of pointers as a second argument and that returns the address of the longest string. If multiple strings are tied for having the longest length, the function should return the address of the first one tied for longest. Test the specialization with an array of five string pointers.

7. Modify Listing 8.14 so that it uses two template functions called SumArray() to return the sum of the array contents instead of displaying the contents. The program now should report the total number of things and the sum of all the debts.

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

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