6.9. From Documentation To Programs

Time: 00:06:43 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

Knowing how to use technical documentation is an important skill for any computer professional. For programmers, this skill includes knowing how to translate language-specific library function descriptions into correct programming statements. C++ documentation comes in various formats from a variety of vendors and organizations. Although C++ documentation varies from one source to the next, their many similarities lessen the challenge of going between them.

Regardless of the source, the most common feature, always located at or very near the top of the document, is a list of one or more function prototypes. Your task as a programmer is to convert these prototypes into working function calls. Recall from an earlier section that prototypes include typing information (the return and parameter types), but typing information is not included in function calls. Furthermore, programmers choose the corresponding argument names based on the underlying programming problem and are not required to use the names appearing in the documentation. C++ documentation typically includes other sections that are not consistently labeled or ordered.

Section Header
Source
Information
cplusplus.com1 Microsoft Developer Network2 Unix/Linux man pages
Function Name function Unlabeled3 NAME
Prototype Follows function name3 Syntax SYNOPSIS
Header File Follows function name3 Requirements SYNOPSIS
Brief Description Derived from function4 Unlabeled3 DESCRIPTION
Input Parameters Parameters SPECIAL VALUES5
Return Value Return Value Output N/A6
Example Example Example N/A7
Related Documentation See also See Also SEE ALSO
Features commonly found in C++ documentation. The figure uses the documentation for the pow library function to compare three different C++ documentation sources. The various sources typically contain the same information but are not always organized or labeled the same. The columns list the typical documentation information and the organization of three different sources. As you gain experience, you will know what information you need or want and learn how to scan the documentation to find it.
  1. Select the C++ 11 tab for the latest version
  2. There are numerous versions of Microsoft documentation on the Internet, and they change frequently
  3. The documentation contains this information, but the section is unlabeled
  4. The section header is derived from the function name or function summary
  5. Similar to "Parameters" but more extensive
  6. Does not provide this section, but it includes the information elsewhere
  7. This documentation did not provide an example, but most do

Decoding Documentation

C++ inherits the C runtime library (CRT) from the C programming language. It is a set of procedural-paradigm functions supporting various semi-primitive operations - common to many problem domains but too high-level for direct language implementation. The CRT consists of functions (with few global variables), and given the ubiquity of their prototypes in the language documentation, the current chapter is the ideal place to begin our discussion of using documentation to write programs. The following discussion is appropriate for the kind of data covered previously, but we'll return to documentation in Chapter 8 as part of our coverage of strings. We begin with error reporting and type aliases.

Error Reporting

The problems global variables cause notwithstanding, many CRT library functions use one nevertheless: errno (short for error number). For example, the sqrt function's domain is all non-negative numbers - passing it a negative number is an illegal operation. The function does not "crash" when called with a negative argument but returns a value representing Not a Number (NaN), and the program continues, propagating the NaN through the subsequent computations. If this error occurs near the beginning of a long, time-consuming calculation, the computer wastes the time between the erroneous call and the end of the calculation. Worse still, if a human doesn't immediately view the results, the error might not be detected until much later.

CRT functions assign a value to errno before returning, so a program must check the value immediately after returning from a function, before calling another, to avoid overwriting the previous value. Functions encode different error conditions with various integer values: zero indicates success, while a non-zero value indicates an error. Several symbolic constants represent specific errors: for example, EDOM (domain error) or ERANGE (range error).

#include <iostream>
#include <cmath>
using namespace std;

int main()
{
    double x = sqrt(-2);

    if (errno != 0)
        cout << errno << ": " << x << endl;
    else
        cout << x << endl;

    return 0;
}
33: -nan(ind)
(b)
perror("sqrt error");
(c)
sqrt error: Domain error
(d)
(a)
Detecting and reporting errors with errno and perror. The <cmath> header file includes <cerrno>, which declares the global variable errno and the function perror.
  1. C++ programs use control statements to detect errors reported through errno. Programs rarely print the error codes because they are integral values conveying little information to programmers or users.
  2. The example program's output. Few programmers and fewer users know that "33" represents a domain error, demonstrating why programs rarely display errno values. cout interprets the value saved in x as -nan, where the negative sign signifies that the error originated with a negative argument.
  3. Many CRT functions can fail in multiple ways. Programmers can replace the cout statement with perror (print error) to augment an error message. The function's argument, highlighted in pink, is the message.
  4. perror prints its argument verbatim, followed by a brief but more meaningful version of the error condition, highlighted in blue.
The text revisits errno and perror later in the chapter, providing additional examples.

Type Aliases: Portable Data Types

Type aliases have some of the same benefits as symbolic constants, creating shorter, more meaningful type names. For example, unsigned long is shortened to size_t. While the new type name is only slightly shorter, it conveys additional meaning to someone reading a program: the variable definition size_t i; informs the reader that the program uses the variable to store the size of a program entity. Similarly, the alias errno_t1 creates a data type appropriate for holding error numbers as described above. Type aliases always improve a program's readability and sometimes make it more portable.

C and C++ are deliberately vague about the size and sign of some specific data types. For example, the size of an int can vary from 2 to 8 bytes (16- to 64-bits), with the exact size chosen to match the word size of the hardware. This ambiguity can sometimes make choosing the best data type for a particular task difficult. One solution is to create "dummy" placeholder data types. The compiler replaces the placeholder names with "real" data types through a typedef expansion: typedef unsigned long size_t;.

Standard portable data type names usually end with _t, and the full name suggests how programs use the type. Programmers typically put the type alias definitions in header files - with the function prototypes when they are tied to specific functions or the standard header file <sys/types.h> when they are more general.

Examples of Documentation To Function Calls

If the function parameters are all simple, fundamental data types (like the pow and sqrt functions), converting the function prototype into a working function call is relatively easy, becoming increasingly easier with practice. However, converting a prototype into working code can become more challenging when the parameters are more complex objects (i.e., instances of structures or classes). The frequent use of default arguments and overloaded functions may also confuse new C++ programmers. The time and effort spent understanding argument passing and related concepts pay substantial dividends here. The following examples demonstrate both simple and more complex library functions and how to convert the prototypes in the documentation into working code.

Simple Function Examples

The pow function demonstrates how programmers convert the documentation's prototype and other descriptions into working C++ code.

Documentation PrototypeProgram Function Call
double pow(double base, double exponent);
double pow(double x, double y);
 
cout << pow(3.14159, 2.0) << endl;
double result = pow(h, 2);
double payment = p * r / (1 - pow(1 + r, -n));
(a)(b)
The pow function: prototype and program.
  1. Various documentation sources name the function parameters differently; some give descriptive names, and others are more generic. However, the function's behavior or operation is independent of the parameter names. Modern implementations of C++ include multiple, overloaded versions of the pow function supporting different data types, ignored here for simplicity. There are many distinct features that we must recognize or infer from the pow function prototype:
    1. A function call requires exactly two arguments and will not compile with too few or too many
    2. The program doesn't modify either argument with a * or & operator, implying that the program passes them by value - they are IN (or input) only
    3. Pass by value also means that the arguments may be any valid expression: a constant, a variable, another function call, or a combination of these
    4. The parameters are double-valued expressions (i.e., they are type double) or a type that can be automatically converted or promoted into a double (char, short, int, long, etc.)
    5. The function will return a value: pow(base, exponent) = baseexponent or pow(x, y) = xy
    6. The return value is type double
    7. The program doesn't modify the returned value with * or & operator, implying the return is by value
  2. Programmers create function calls by dropping the data type (i.e., "double") and replacing the parameters with values, variables, or other expressions appropriate for the client program calling the function. The examples assume that the program defines the variables h, p, r, and n

The ftime Library Function

Although Linux no longer provides the ftime function, the text uses it to demonstrate a "real world" use of a structure passed-by-pointer and other documentation features, including a confusing problem programmers encounter when first learning to convert documentation into programs. ftime gets the time, measured in seconds and milliseconds, since "the world began" or, more formally, since the epic. For Unix, Linux, and macOS, the epic is January 1, 1970; for Windows, it is January 1, 1980. The function gets the elapsed time from the computer hardware via the operating system, so it represents a special kind of function called a system call. System calls are often unique to a given operating system, and even when two systems support the same call, the function and argument names may differ, as the next figure illustrates.

WindowsUnix/Linux/macOS
#include <sys/timeb.h>
errno_t _ftime_s(struct _timeb *timeptr);
#include <sys/timeb.h>
int ftime(struct timeb *tp);
(a)(b)
The ftime system call. The header file, <sys/timeb.h>, contains the structure specification and function prototype. The sys directory contains this header file along with other system-specific features. The ftime structure and the system call were originally designed for the older C programming language, and the header file remains unmodified for C++, making the keyword "struct" necessary. The different parameter names (timeptr and tp) are insignificant, but programs must use the specified function and structure names appearing in the documentation.
  1. The Microsoft Developers Network lists three different functions. Choose the first version, and the compiler will do the rest. Depending on the configuration settings, Visual Studio may report a warning or error if a program doesn't use the specified type alias.
  2. The single Unix/Linux/macOS function is relatively straightforward.
WindowsUnix/Linux/macOS
struct _timeb* start;		// error!!
_ftime_s(start);
struct timeb* start;		// error!!
ftime(start);
struct _timeb start;		// correct
_ftime_s(&start);
struct timeb start;		// correct
ftime(&start);
A common documentation-to-code error. The prototypes for both ftime versions indicated a pointer parameter, tempting programmers to define the client argument as a pointer. The highlighted statements are syntactically correct but represent logical errors. The system call returns multiple values by filling the fields of a structure object, requiring an INOUT passing method. The correct prototype interpretation is pass-by-pointer - the client program creates a structure object and passes its address, found with the address-of operator, to the system call.
#include <iostream>
#include <sys/timeb.h>
using namespace std;

int main()
{
	struct _timeb	cur_time;

	_ftime_s(&cur_time);

	cout << "Time since the epic: " << cur_time.time;
	cout << " seconds and " << cur_time.millitm << " milliseconds" << endl;

	return 0;
}
ftime: Windows pointer example. _ftime_s is a system call that fills a structure object with the current time. The structure has two fields, one for seconds and the other for milliseconds. The main function is the client using f_time_s and is where the structure object is defined. The client gets the address of the structure object, cur_time, with the address-of operator and passes it by pointer to _ftime_s, which fills it with the time data.
#include <iostream>
#include <sys/timeb.h>
using namespace std;

int main()
{
        struct timeb    cur_time;

        ftime(&cur_time);

        cout << "Time since the epic: " << cur_time.time;
        cout << " seconds and " << cur_time.millitm << " milliseconds" << endl;

        return 0;
}
ftime: Unix / Linux / macOS pointer example. POSIT-complient operating systems have slightly different names for the time structure and the system call. Otherwise, this example is identical to the previous one.


  • Although I've found some documentation suggesting that errno_t is included in the current ANSI standard, Microsoft's Visual Studio compiler is the only compiler I've found that supports it. Consider it a Microsoft-only feature.
  •