constexpr
FunctionsIn § 6.3.2 (p. 224) we wrote a small function that returned a reference to the shorter of its two string
parameters. The benefits of defining a function for such a small operation include the following:
• It is easier to read and understand a call to shorterString
than it would be to read and understand the equivalent conditional expression.
• Using a function ensures uniform behavior. Each test is guaranteed to be done the same way.
• If we need to change the computation, it is easier to change the function than to find and change every occurrence of the equivalent expression.
• The function can be reused rather than rewritten for other applications.
There is, however, one potential drawback to making shorterString
a function: Calling a function is apt to be slower than evaluating the equivalent expression. On most machines, a function call does a lot of work: Registers are saved before the call and restored after the return; arguments may be copied; and the program branches to a new location.
inline
Functions Avoid Function Call OverheadA function specified as inline
(usually) is expanded “in line” at each call. If shorterString
were defined as inline
, then this call
cout << shorterString(s1, s2) << endl;
(probably) would be expanded during compilation into something like
cout << (s1.size() < s2.size() ? s1 : s2) << endl;
The run-time overhead of making shorterString
a function is thus removed.
We can define shorterString
as an inline function by putting the keyword inline
before the function’s return type:
// inline version: find the shorter of two strings
inline const string &
shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
The inline
specification is only a request to the compiler. The compiler may choose to ignore this request.
In general, the inline
mechanism is meant to optimize small, straight-line functions that are called frequently. Many compilers will not inline a recursive function. A 75-line function will almost surely not be expanded inline.
constexpr
FunctionsA constexpr
function is a function that can be used in a constant expression (§ 2.4.4, p. 65). A constexpr
function is defined like any other function but must meet certain restrictions: The return
type and the type of each parameter in a must be a literal type (§ 2.4.4, p. 66), and the function body must contain exactly one return
statement:
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // ok: foo is a constant expression
Here we defined new_sz
as a constexpr
that takes no arguments. The compiler can verify—at compile time—that a call to new_sz
returns a constant expression, so we can use new_sz
to initialize our constexpr
variable, foo
.
When it can do so, the compiler will replace a call to a constexpr
function with its resulting value. In order to be able to expand the function immediately, constexpr
functions are implicitly inline
.
A constexpr
function body may contain other statements so long as those statements generate no actions at run time. For example, a constexpr
function may contain null statements, type aliases (§ 2.5.1, p. 67), and using
declarations.
A constexpr
function is permitted to return a value that is not a constant:
// scale(arg) is a constant expression if arg is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
The scale
function will return a constant expression if its argument is a constant expression but not otherwise:
int arr[scale(2)]; // ok: scale(2) is a constant expression
int i = 2; // i is not a constant expression
int a2[scale(i)]; // error: scale(i) is not a constant expression
When we pass a constant expression—such as the literal 2
—then the return is a constant expression. In this case, the compiler will replace the call to scale
with the resulting value.
If we call scale
with an expression that is not a constant expression—such as on the int
object i
—then the return is not a constant expression. If we use scale
in a context that requires a constant expression, the compiler checks that the result is a constant expression. If it is not, the compiler will produce an error message.
inline
and constexpr
Functions in Header FilesUnlike other functions, inline
and constexpr
functions may be defined multiple times in the program. After all, the compiler needs the definition, not just the declaration, in order to expand the code. However, all of the definitions of a given inline
or constexpr
must match exactly. As a result, inline
and constexpr
functions normally are defined in headers.
Exercise 6.43: Which one of the following declarations and definitions would you put in a header? In a source file? Explain why.
(a) inline bool eq(const BigInt&, const BigInt&) {...}
(b) void putValues(int *arr, int size);
Exercise 6.44: Rewrite the isShorter
function from § 6.2.2 (p. 211) to be inline
.
Exercise 6.45: Review the programs you’ve written for the earlier exercises and decide whether they should be defined as inline
. If so, do so. If not, explain why they should not be inline
.
Exercise 6.46: Would it be possible to define isShorter
as a constexpr
? If so, do so. If not, explain why not.