First-class citizens¶
In programming language design, an entity that a programming language can
express is called a ‘first-class citizen’, or just ‘first class’,
if it supports the full range of possible operations—things such as
storage in a variable, assignment, use as an argument to a function call,
and use as a return value from a function call. Other things that exist
in the language but don’t have that full flexibility are called ‘second
class’. In C, for example, values of type int
are first class,
while a type itself is second-class—function-like objects such as
sizeof
that can take a type as argument as in sizeof(int)
must actually be implemented by the compiler or using preprocessor macros.
Structures in C are first class—they have assignment, which works by assigning corresponding members, and they can be passed into and out of functions.
It is commonly discussed that C does not allow functions to return multiple values. There is a single return type for each function declaration, and no syntax for multiple assignment. However, the ‘single’ value returned from a function can be a structure, and this can be used to return small combinations of data such as a pair of numbers.
For example, the standard library has a function div
, with
the following signature.
#include <stdlib.h>
div_t div(int numerator, int denominator);
It computes integer division and simultaneously returns a quotient
and remainder. The return type div_t
is a typedef
for a struct that is defined something like this.
typedef struct {
int quot;
int rem;
} div_t;
An implementation of div
could look like this.
div_t div(int numerator, int denominator)
{
div_t result;
result.quot = numerator / denominator;
result.rem = numerator % denominator;
return result;
}
(As written, this uses the /
and %
operator separately,
but in assembly we have the div
instruction which can compute both
quotient and remainder at once. If we were concerned about that level
of optimization, it might become relevant to check that the compiler
was leveraging the hardware appropriately when compiling this function,
or replace this C implementation with an assembly one and link that into
the library.)
It is usual to pass structure around as pointers (in C, or as references in C++) to avoid copying, but when copying is desirable or when it is actually cheap (as in this case with only two integers) there is no reason to avoid using structures as pass-by-value arguments or as return values. In fact, wrapping an array in a structure is a way to force C to make an item-by-item copy on your behalf.
struct five_ints {
int nums[5];
};
typedef struct five_ints five_ints;
void example(five_ints fi)
{
fi.nums[0] = 27; /* doesn't change original */
}
int main()
{
five_ints orig = {1, 2, 3, 4, 5};
example(orig);
}
Conversely, some libraries, such as the GNU Multiple Precision Arithmetic Library, use the opposite trick—an array of one element which is a structure—to get implicit conversion to pointers and pass structure types ‘by reference’.
struct point {
double x;
double y;
};
typedef struct point point_t[1];
void example(struct point *p)
{
p->x = 3; /* change original through pointer */
}
int main()
{
point_t p = {6, 7};
example(p); /* implicitly pass address rather than value */
}