CS253: Software Development with C++

Spring 2020

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

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++ -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++ -c ~cs253/Example/mangle.cc

$ nm mangle.o
0000000000000000 T _Z1bonehead

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'

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(string s) {    // pass by value
    return s.empty();
}

int main() {
    string s(10'000, 'x');
    for (int i=0; i<10'000'000; i++)
        foo(s);
    cout << "Done.\n";
}
Done.

Real time: 2.99 seconds

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

Efficiency of pass by constant reference

bool foo(const string &s) { // pass by constant reference
    return s.empty();
}

int main() {
    string s(10'000, 'x');
    for (int i=0; i<10'000'000; i++)
        foo(s);
    cout << "Done.\n";
}
Done.

Real time: 64.7 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 always 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.