Structure Arguments
Structures may be passed as arguments to functions; they may also be returned from functions. Specify the structure type as the parameter or return type.
We can use this to get around the inconvenience of the missing structure aggregate operations. Although we cannot compare two structures with == or !=, we can write a function to supply the necessary operation, like this:
bool equals(Date lhs, Date rhs)
{
return lhs.month == rhs.month &&
lhs.day == rhs.day && lhs.year == rhs.year;
}
The function equals() takes two Date arguments and returns true if they are equal and false, otherwise. Use the function like this:
if (equals(d1, d2)) cout << "equal" << endl;
The parameter names lhs and rhs are shorthand for left-hand-side and right-hand-side, and are commonly use with functions that mimic the built-in operators.
By-Value or By-Reference
Let's look at that last example again:
if (equals(d1, d2)) cout << "equal" << endl;
Here, the two arguments, d1 and d2, are passed by value, which means that the parameter variables lhs and rhs are initialized by making a copy of the entire Date structure when calling the function.
In this particular case, the cost (time and memory) of making that copy is not very high; but, if the structure had more data members, calling this function could be very expensive. For structure, class and library types, we can avoid that cost by:
- Using const reference if the function should not modify the argument.
- Use non-const reference if the intent is to modify the actual argument.
Given these guidelines, a more correct version of equals() would look like this:
bool equals(const Date& lhs, const Date& rhs)
{
return lhs.month == rhs.month &&
lhs.day == rhs.day && lhs.year == rhs.year;
}
In general, never pass a class or structure type by value to a function.
This is a fundamental difference in the way that Java and C++ object types work. In Java and C#, objects have reference semantics—the object variables do not contain the actual object members. In C++, objects have value semantics; the actual data members are stored inside the object variables.
A Compare Function
Of course, when comparing dates, we don't just want to know if they are equal. More often we want to know of one date is earlier or later than another.
Instead of writing additional functions, such as lessThan() and greaterThan(), we can write a single function named compare(), which returns 0 if the two dates are equal, and a negative or positive number if the first date is earlier than, or later than the second.
This is a very common, three-way pattern. Here's a possible solution
bool compare(const Date& lhs, const Date& rhs)
{
if (lhs.year == rhs.year &&
lhs.month == rhs.month && lhs.day == rhs.day) {
return 0; // all the same
}
else if (lhs.year < rhs.year) {
return -1; // year is earlier
}
else if (lhs.year == rhs.year && lhs.month < rhs.month) {
return -1; // year same but month earlier
}
else if (lhs.year == rhs.year && lhs.month == rhs.month &&
lhs.day < rhs.day) {
return -1; // year, month same, but day earlier
} else {
return 1; // must be after
}
}
Returning Structures
Let's look at a variation on one of the problems given earlier on a Programming Exam. In the original exam, the two roots of the quadratic equation were returned via a pair of output parameters. Now that we have structured types, we can write the function a little more naturally, by returning structure.
Write a function
quadratic()
which computes roots of quadratic equations. A quadratic equation is one of the form:ax2 + bx + c = 0
.Your function has three input parameters, the integer coefficients
a
,b
, andc
. The function returns astruct
containing twodouble
members:root1
androot2
. Assume that the function has two real roots. The quadratic formula is:
Using structures, you could write the function like this:
struct Roots { double root1, root2; };
Roots quadratic(int a, int b, int c)
{
double determinant = b * b - 4 * a * c;
Roots result;
result.root1 = (-b + sqrt(determinant)) / (2 * a);
result.root2 = (-b - sqrt(determinant)) / (2 * a);
return result;
}
Then, you could call the function like this:
Roots r = quadratic(1, -3, -4);
cout << "roots->" << r.root1 << ", " << r.root2 << endl;
Structured Bindings
Let's look at that last function call once again:
Roots r = quadratic(1, -3, -4);
cout << "roots->" << r.root1 << ", " << r.root2 << endl;
Notice that it is up to the programmer to "unpack" the returned structure. C++17 added a new feature to the language that makes it easier to retrieve several returned values from a function. These are called structured bindings.
With structured bindings, you can automatically unpack the structure into a special form of auto declared variables like this:
auto [r1, r2] = quadratic(1, -3, -4);
cout << "roots->" << r1 << ", " << r2 << endl;
Note that you do not need to specify the names of the data members in the returned structure variable, nor the types of the local variables r1 and r2. The structure is unpacked and root1 is assigned to r1 and root2 is assigned to r2. They are both automatically declared as type double.