CS253: Software Development with C++

Spring 2023

Functions

Show Lecture.Functions as a slide show.

CS253 Functions

made at imgflip.com

Definition

Remember that a method is just a function associated with a class. Everything said here applies equally well to methods, because methods are functions.

Functions

By & large, C++ functions look like Java functions:

int sum(int a, int b) {
    return a+b;
}

int main() {
    cout << sum(2, 40) << '\n';
}
42

Default Parameters

The trailing arguments can have default values:

int sum(int a, int b = 13) {
    return a+b;
}

int main() {
    cout << sum(2, 40) << '\n'
         << sum(8)     << '\n';
}
42
21
Are there two functions called sum, or just one?

There’s only one, which always takes two arguments. If you call sum with a single argument, the compiler adds a second argument of 13.

Default Parameters

All default arguments must be piled up at the end:

int sum(int a = 123, int b = 456) {
    return a+b;
}

int main() {
    cout << sum(2, 40) << '\n'
         << sum(8)     << '\n'
         << sum()      << '\n';
}
42
464
579
int bad(int c=8, long d) {  // 🦡
    return c+d;
}

int main() {
    return bad(5);
}
c.cc:1: error: default argument missing for parameter 2 of ‘int bad(int, long 
   int)’

Function Overloading

Like Java, C++ can have multiple versions of the same function that take different arguments:

int sum(int a, int b) { return a+b; }
int sum(int a) { return a+456; }
int sum() { return 123+456; }

int main() {
    cout << sum(2, 40) << '\n'
         << sum(8)     << '\n'
         << sum()      << '\n';
}
42
464
579
Are there three functions called sum, or just one?

Three. They’re right up there.

Function Overloading

The types don’t have to be the same, either:

int sum(int a, int b) { return a+b; }
double sum(int a) { return a+4.5; }
string sum(double) { return "I can’t deal with double"; }
const char *sum() { return "You are a bonehead"; }

int main() {
    cout << sum(2, 40)   << '\n'
         << sum(8)       << '\n'
         << sum(123.456) << '\n'
         << sum()        << '\n';
}
42
12.5
I can’t deal with double
You are a bonehead

For sum(double), the unused argument isn’t given a name, to avoid warnings about unused variables.

Implementation

Name Mangling

Return type doesn’t matter

int foo() { return 1; }
double foo() { return 2.0; } // 🦡
int main() {
    return 0;
}
c.cc:2: error: ambiguating new declaration of ‘double foo()’

Name Mangling

$ cat ~cs253/Example/overload.cc
int sum(int a, int b) { return a+b; }
double sum(int a) { return a+4.5; }
const char *sum() { return "You are a bonehead"; }
bool sum(double, int, short) { return false; }

$ g++ -Wall -c ~cs253/Example/overload.cc

$ nm --numeric-sort overload.o 2>&-
0000000000000000 T _Z3sumii
0000000000000014 T _Z3sumi
000000000000002e T _Z3sumv
0000000000000039 T _Z3sumdis

$ nm --numeric-sort --demangle overload.o 2>&-
0000000000000000 T sum(int, int)
0000000000000014 T sum(int)
000000000000002e T sum()
0000000000000039 T sum(double, int, short)

More Name Mangling

$ cat ~cs253/Example/mangle.cc
void b(unsigned __int128,
       __int128,
       long double,
       unsigned char,
       long double,
       signed char,
       double) {
}

$ g++ -Wall -c ~cs253/Example/mangle.cc

$ nm mangle.o 2>&-
0000000000000000 T _Z1bonehead

Do not memorize the various encodings for types. It varies between compilers, anyway.

auto

// The “Golden Ratio”:
auto φ() {
    return (1+sqrt(5.0L))/2;
}

auto fact() {
    cout << "φ ≈ " << φ() << '\n';
}

int main() {
    fact();
    return 0;
}
φ ≈ 1.61803

The return type can be declared as auto, which means that the actual type will be deduced by what is returned. auto is not a type. It means “Compiler, you figure out the real type.”

What is the return type of fact?

void. It has no return statement.
It’s certainly not auto, because auto is not a type, any more than your mother’s name is “Mom”.

What is the return type of φ?

The type of 5.0L is long double, so sqrt(5.0L) is long double, so 1+sqrt(5.0L) is long double, so (1+sqrt(5.0L))/2 is long double, so … long double.

Caution

The auto type deduction tolerates no ambiguity.

auto age_of_earth(bool science) {
    if (science)
        return 4.54e9;       // science: 4.54 billion years ago
    else
        return 4004-1+2024;  // Archbishop Ussher: 4004BC 🦡
}

int main() {
    cout << age_of_earth(true);
}
c.cc:5: error: inconsistent deduction for auto return type: ‘double’ and 
   then ‘int’

Pass by value

By default, arguments are passed by value.

void foo(int n) {
    ++n; ++n;
}

int main() {
    int v = 42;
    foo(v);
    cout << v << '\n';
}
42

The variable n is a copy of v.

Pass by address

A C programmer might pass by address:

void foo(int *n) {
    *n = 99;
}

int main() {
    int v = 42;
    foo(&v);
    cout << v << '\n';
}
99

The actual parameter is &v, the address of v. This is fine C, but C++ can do better. Use a reference, instead.

Pass by reference

C++ can pass a variable by reference:

void foo(int &n) {
    ++n; ++n;
}

int main() {
    int v = 42;
    foo(v);
    cout << v << '\n';
}
44

The variable n is an alias of v. This is often implemented as a pointer that gets * applied automatically.

Pass by constant address

You can use a read-only pointer:

void foo(const int *n) {
    *n = 99;  // 🦡
}

int main() {
    int v = 42;
    foo(&v);
    cout << v << '\n';
}
c.cc:2: error: assignment of read-only location ‘* n’

As before, avoid pointer arguments in favor of reference arguments.

Pass by reference

You can use a read-only reference:

void foo(const int &n) {
    ++n; ++n;  // 🦡
}

int main() {
    int v = 42;
    foo(v);
    cout << v << '\n';
}
c.cc:2: error: increment of read-only reference ‘n’

Efficiency

Efficiency of pass by value

bool foo(vector<int> v) {    // 🦡 pass by value
    return v.empty();
}

int main() {
    vector<int> w(2500);
    for (int i=0; i<10'000'000; i++)
        foo(w);
    cout << "Done.\n";
}
Done.

Real time: 1.99 seconds

We made ten million copies of a ten-thousand-byte vector, and so copied 100 gigabytes. Slow! 🐌

Efficiency of pass by constant reference

bool foo(const vector<int> &v) {    // pass by constant reference
    return v.empty();
}

int main() {
    vector<int> w(2500);
    for (int i=0; i<10'000'000; i++)
        foo(w);
    cout << "Done.\n";
}
Done.

Real time: 5.53 ms

No copies were made! 😎

Rule of thumb

double sqrt(double d);
int count_spaces(const string &s);

void get_physical_info(int &height, int &weight);
void remove_spaces(string &s);
void read_picture(Picture &pic, ostream &os);
void write_picture(const Picture &pic, ostream &os);

Arrays

Arrays are special. They’re automatically passed by reference:

void foo(int a[]) {
    a[0] = 911;
}

int main() {
    int numbers[] = {555-1212, 867-5309};  // dashes⁇
    foo(numbers);
    cout << numbers[0];
}
911

This only applies to old-style C arrays, not std::vector or std::array.