CS253: Software Development with C++

Spring 2021

Functions

Show Lecture.Functions as a slide show.

CS253 Functions

made at imgflip.com

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

Functions can have default trailing arguments:

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

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

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

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

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

Notice how, 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
0000000000000000 T _Z3sumii
0000000000000014 T _Z3sumi
000000000000002e T _Z3sumv
0000000000000039 T _Z3sumdis

$ nm --numeric-sort --demangle overload.o
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
0000000000000000 T _Z1bonehead

I do not expect you to memorize the various encodings for types. It varies between compilers, anyway.

auto

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.”

auto pi() {
    return 3.14159;
}

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

int main() {
    fact();
    return 0;
}
π ≈ 3.14159

Caution

The auto type deduction tolerates no ambiguity.

auto foo(bool b) {
    constexpr short default_value = 0;
    if (!b)
        return default_value;
    else
        return 42;
}

int main() {
    return foo(false);
}
c.cc:6: error: inconsistent deduction for auto return type: 'short int' 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 gets 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

Note that the actual parameter is &v, the address of v. This is rarely the best solution. 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.95 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.41 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.