Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 4 – Functions, files and scope

C++ functions: declarations, definitions, returns

Declare and define C++ functions, return several results in a struct, flag ignored results with [[nodiscard]] and see what a missing return does.

  • Beginner
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Trace a call from its argument to the parameter and back to the returned value
  • Declare a function before the first call, and fix an undeclared-name error
  • Write a function that returns several results in a struct instead of filling output parameters
  • Use [[nodiscard]] so that an ignored result makes the compiler warn
  • Prevent a missing return with -Wreturn-type and -Werror=return-type

Before you start

What you’ll learn · Before you start

What you will learn

  • Trace a call from its argument to the parameter and back to the returned value
  • Declare a function before the first call, and fix an undeclared-name error
  • Write a function that returns several results in a struct instead of filling output parameters
  • Use [[nodiscard]] so that an ignored result makes the compiler warn
  • Prevent a missing return with -Wreturn-type and -Werror=return-type

Before you start

On this page

A rule written out in three places has to be fixed in three places: change how marks become grade points, and every copy must change the same way. A function gives the rule one name and one home. It takes its inputs as parameters, does one job and hands back a result, so every caller uses it with one call, grade_points(92), and a fix happens once.

You will define and call functions, declare them before use, return several results in a struct and make the compiler warn when a result is ignored. You will also see what happens when a function forgets its return: one file printed 1, printed 4, trapped and crashed in four builds. Every program was built with Apple clang 21 and GCC 16.

A call copies the argument, runs the body and returns a value

Here is a complete function and a main that calls it three times. Its first line says everything a caller needs: int is the type of the result, grade_points is the name, and (int marks) is the list of parameter: A variable named in a function's parameter list, such as marks in int grade_points(int marks). Each call creates it anew and initialises it from the argument the call passes.. The body, between the braces, runs on each call:

One function, three calls C++ · grade_points.cpp
// grade_points.cpp: one function, defined above main and called three times.
#include <print>

// Turns marks out of 100 into grade points on a 10-point scale.
int grade_points(int marks) {
    if (marks >= 90) return 10;
    if (marks >= 75) return 8;
    if (marks >= 60) return 6;
    if (marks >= 40) return 4;
    return 0;
}

int main() {
    int maths = 92;
    std::println("maths: {} marks, {} points", maths, grade_points(maths));
    std::println("physics: 67 marks, {} points", grade_points(67));
    int total = grade_points(81) + grade_points(39);
    std::println("two more subjects: {} points together", total);
}

Output

maths: 92 marks, 10 points
physics: 67 marks, 6 points
two more subjects: 8 points together

Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined grade_points.cpp -o grade_points && ./grade_points

The program printed 10, 6 and 8 points, and the GCC build printed the same three lines. A return statement ends the function at once and hands its value back; the rest of the body is skipped, which is why the five return lines can follow one another without else. Step through the first call and watch where 92 goes:

Interactive · message by message One call of grade_points, from the argument to the result Step through grade_points(maths) in grade_points.cpp. The arrows are the call and the value it returns.
One call of grade_points, from the argument to the result, step 7 of 7. Messages so far: main to grade_points: grade_points(92); grade_points to main: return 10; main to grade_points: two calls: 81 and 39; grade_points to main: return 8 and return 0.maintotal is 8 + 0 = 8grade_pointsfinishedgrade_points(92)return 10two calls: 81 and 39return 8 and return 0

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. The argument first. main reaches grade_points(maths). Before the call starts, its argument is evaluated: maths holds 92.
  2. 92 starts the call. The call creates a new variable for itself, the parameter marks, and initialises it with 92. It is a copy, so nothing the function does to marks can change maths.
  3. The body runs. The first test, marks >= 90, is true, so return 10 runs. A return ends the function at once: the other four tests are never evaluated.
  4. 10 comes back. The function hands 10 back, and marks, which belonged to this call, is gone. In main the expression grade_points(maths) now has the value 10.
  5. println uses the value. std::println receives 92 and 10 as arguments and prints maths: 92 marks, 10 points, the first line of the recorded output.
  6. Two calls in one sum. grade_points(81) + grade_points(39) makes two separate calls, each with a marks of its own. The standard does not say which of the two runs first, and here it does not matter.
  7. 8 and 0 make 8. The calls return 8 and 0, so total is 8, the third line the program printed. Each call started from its own copy of its argument.

When main evaluates grade_points(maths), the argument: The value a call passes to a function, such as maths in grade_points(maths). It is evaluated before the call starts and initialises the matching parameter. maths is evaluated first, and its value, 92, initialises marks, a new variable that belongs to this one call. The body runs until a return, and the call expression takes the returned value, 10, as if main had written 10 there. A call is an expression like any other, so grade_points(81) + grade_points(39) simply adds 8 and 0.

Because marks is a copy, the function cannot change maths in main. Copying is how C++ passes arguments unless you ask for something else; the parameter-passing lesson shows references, which let a function work on the caller’s variable instead. A function that only does something, such as printing, has the return type void: it may end with a bare return; or simply reach its closing brace.

Checkpoint

Check: What a call does with its argument and its result

  1. Question 1 of 2 grade_points gets a new first line, marks = 0;. What does maths in main hold after the call grade_points(maths)?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 92

    Each call creates its parameter and initialises it with the value of the argument. The function works on that copy, so maths keeps 92 whatever grade_points does to marks. The function would then return 0 for every mark, but that is a different question.

  2. Question 2 of 2 With grade_points as in grade_points.cpp, what is grade_points(75) + grade_points(60) + grade_points(59)?

    Type a number.

    A hint

    Each call returns at the first test that is true.

    Show the answer: Question 2 of 2

    Answer: 18 points

    75 fails marks >= 90 and passes marks >= 75, so it returns 8. 60 passes marks >= 60 and returns 6. 59 passes only marks >= 40 and returns 4. Three calls, three values: 8 + 6 + 4 = 18.

The compiler must see a declaration before the call

The compiler reads a file from the top down, and it accepts a call only when it already knows the function’s name, parameter types and return type. Here main comes first and calls a function defined below it:

Calling a function the compiler has not seen yet, and the fix

C++ (call_too_early.cpp) · call_too_early.cpp

// call_too_early.cpp: main calls grade_points before the compiler has seen it.
#include <print>

int main() {
    std::println("{} points", grade_points(92));
}

int grade_points(int marks) {
    return marks >= 40 ? marks / 10 : 0;
}

C++ (declare_first.cpp) · declare_first.cpp

// declare_first.cpp: a declaration at the top lets main call a function defined below.
#include <print>

int grade_points(int marks);  // declaration: name, parameter types, return type, then ;

int main() {
    std::println("{} points", grade_points(92));
}

int grade_points(int marks) {  // definition: the same first line, then the body
    if (marks >= 90) return 10;
    if (marks >= 75) return 8;
    if (marks >= 60) return 6;
    if (marks >= 40) return 4;
    return 0;
}
Both compilers reject the first file Python · errors_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra call_too_early.cpp -o call_too_early
g++-16 -std=c++23 -Wall -Wextra call_too_early.cpp -o call_too_early
clang++ -std=c++23 -Wall -Wextra declare_first.cpp -o declare_first && ./declare_first
""")

Output

$ clang++ -std=c++23 -Wall -Wextra call_too_early.cpp -o call_too_early
call_too_early.cpp:5:31: error: use of undeclared identifier 'grade_points'
    5 |     std::println("{} points", grade_points(92));
      |                               ^~~~~~~~~~~~
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra call_too_early.cpp -o call_too_early
call_too_early.cpp: In function 'int main()':
call_too_early.cpp:5:31: error: 'grade_points' was not declared in this scope
    5 |     std::println("{} points", grade_points(92));
      |                               ^~~~~~~~~~~~
[exit status 1]
$ clang++ -std=c++23 -Wall -Wextra declare_first.cpp -o declare_first && ./declare_first
10 points

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py

Apple clang says use of undeclared identifier 'grade_points'; GCC says 'grade_points' was not declared in this scope. Both point at line 5, column 31, where the call is. There are two fixes: move the definition above main, or keep the order and add a function declaration: A function's return type, name and parameter types ended by a semicolon, as in int grade_points(int marks);. It lets code call the function before, or without, seeing its body. at the top, as declare_first.cpp does: the first line of the function followed by a semicolon instead of a body. Step through what happens to that file, from its first line to the running program:

Interactive · stage by stage What the compiler reads before it accepts a call Step through declare_first.cpp from its first line to the running program. The compiler never looks ahead.
What the compiler reads before it accepts a call, step 6 of 6. Showing Line 4: the declaration (known: (int) to int), Line 7: the call (accepted), Line 10: the definition (defined once), One program (printed 10 points). In focus: One program.looked up aboveLine 4: the declarationknown: (int) to intLine 7: the callacceptedLine 10: the definitiondefined onceOne programprinted 10 points

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A promise at line 4. The compiler reads the file from the top. Line 4 ends in a semicolon instead of a body, so it is a declaration: grade_points takes one int and returns an int.
  2. The call at line 7. main calls grade_points(92). The compiler looks the name up among the declarations above the call, finds line 4, and checks that 92 can initialise an int parameter.
  3. Without line 4. In call_too_early.cpp nothing above the call declares the name, so the lookup finds nothing: both compilers stopped at line 5, column 31, where the call is.
  4. The body at line 10. With the declaration in place, line 10 is the definition: the same first line followed by the body. It keeps the promise of line 4.
  5. One definition. A program may declare a function as often as it likes but must define an ordinary function exactly once. With several files, the linker joins each call to that one definition, and stops with an undefined symbol when there is none.
  6. It runs. The program runs and prints 10 points, the last line of the session.

A declaration promises that the function exists; the definition keeps the promise. The parameter names are optional in a declaration (int grade_points(int); declares the same function), but they tell the reader what each argument means. Declarations may be repeated as often as you like, while a program holds exactly one definition of each ordinary function: the one-definition rule: The rule that a program holds exactly one definition of each ordinary function it uses. An inline function may be defined in every translation unit that uses it, as long as all the definitions are identical. More in “How C++ compiles: preprocess, compile and link”.

That is how programs of several files work. A header holds the declarations, every .cpp file that calls the functions includes it, and one .cpp file holds the definitions. When a declared function has no definition anywhere, every file still compiles, and the linker: The last stage of a build: it joins the object files of a program and the libraries it uses into one executable, matching every symbol a file uses with exactly one definition. More in “How C++ compiles: preprocess, compile and link” then reports an undefined symbol. The exercise at the end of this lesson is built from three files in exactly this way.

Checkpoint

Check: Declarations, definitions and the order of a file

  1. Question 1 of 2 Which of these lines is a declaration of twice, and not a definition?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: int twice(int n);

    A declaration ends with a semicolon instead of a body. It may appear many times, in a header and in every file that includes it, while the definition, the version with braces, appears once in the whole program.

  2. Question 2 of 2 tax.cpp declares int tax(int amount); at the top and calls tax(500) in main, but no file of the program defines tax. What happens when you build it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The file compiles, and the linker stops with an undefined symbol for tax

    Compiling and linking are separate steps. The compiler checks each call against a declaration, and the linker joins each call to the one definition. A declared function that nobody defines passes the first step and fails the second.

Return a struct when a function has several results

A function returns one value. When a computation produces several values that belong together, return a small struct that holds them. Older code, and many C libraries, use output parameter: A reference or pointer parameter that a function writes a result into, so the caller must create the variable before the call. Returning a value, or a struct of several values, is usually clearer. instead: the caller passes variables, and the function writes into them. Both programs split 155 minutes into hours and minutes, one each way:

Two results: a struct, or output parameters

Return a struct · split_struct.cpp

// split_struct.cpp: a function with two results returns them together in a struct.
#include <print>

struct Duration {
    int hours;
    int minutes;
};

Duration split_minutes(int total_minutes) {
    return {total_minutes / 60, total_minutes % 60};
}

int main() {
    Duration film = split_minutes(155);
    std::println("The film runs {} h {} min", film.hours, film.minutes);

    // a structured binding names both parts
    auto [hours, minutes] = split_minutes(1000);
    std::println("1000 minutes is {} h {} min", hours, minutes);
}

Output

The film runs 2 h 35 min
1000 minutes is 16 h 40 min

Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined split_struct.cpp -o split_struct && ./split_struct

Output parameters · split_out_params.cpp

// split_out_params.cpp: the same job with output parameters, as older code does it.
#include <print>

// The & makes hours and minutes refer to the caller's variables (see the next lesson).
void split_minutes(int total_minutes, int& hours, int& minutes) {
    hours = total_minutes / 60;
    minutes = total_minutes % 60;
}

int main() {
    int hours;    // must exist before the call, with no sensible value yet
    int minutes;
    // which arguments are inputs? The call does not say.
    split_minutes(155, hours, minutes);
    std::println("The film runs {} h {} min", hours, minutes);
}

Output

The film runs 2 h 35 min

Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined split_out_params.cpp -o split_out_params && ./split_out_params

Both print The film runs 2 h 35 min, but their calls read differently:

At the call Return a struct Output parameters
What is written split_minutes(155) split_minutes(155, hours, minutes)
Which values go in The one in the brackets Not shown by the call
The results Arrive with their names Need variables declared first

Duration film = split_minutes(155); says what goes in and what comes out. split_minutes(155, hours, minutes); looks like a call with three inputs, and it forces main to create hours and minutes before the call with no sensible value in them. With the struct, auto [hours, minutes] = split_minutes(1000); names both parts in one line, a A declaration such as auto [hours, minutes] = f(); that gives a name to each member of the struct, pair or tuple a call returns, in one line., which a later module covers with pairs and tuples. Rules F.20 and F.21 of the C++ Core Guidelines say the same thing: hand results back as return values, and group several of them in a struct. Returning a struct is cheap too, because C++ constructs the returned object right where the caller’s variable lives, as the parameter-passing lesson measures.

When an output parameter is still right

A function that must change a large object the caller already has, such as a list of a million marks to sort in place, takes it by reference and changes it there. That is an in-out parameter, not a way to return new results.

Checkpoint

Check: Several results in a struct

  1. Question 1 of 2 A function turns a number of seconds into hours, minutes and seconds. Which declaration follows the advice of this lesson?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Clock split_seconds(int total);, with struct Clock { int hours; int minutes; int seconds; };

    The C++ Core Guidelines (F.20, F.21) prefer returned values to output parameters, and a struct when there are several. split_struct.cpp did exactly this with hours and minutes.

  2. Question 2 of 2 When is changing an argument through a reference parameter still the right design?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: When the function must change a large object the caller already has, such as sorting a list of a million marks in place

    Output parameters are a way to hand back new results, and returning is clearer for that. A reference parameter that changes an existing object in place, an in-out parameter, is a different job and still the right tool.

[[nodiscard]] turns an ignored result into a warning

Some functions exist only for their result. If a caller throws that result away, the call was pointless, or worse, a bug. Put the attribute [[nodiscard]] before the return type, and the compiler warns whenever the result of a call is ignored. Line 20 below is the classic mistake:

nodiscard.cpp C++ · nodiscard.cpp
// nodiscard.cpp: two functions whose results must not be thrown away.
#include <cctype>
#include <print>
#include <string>

[[nodiscard]] bool is_leap_year(int year) {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

// The reason in the brackets becomes part of the warning (C++20).
[[nodiscard("it returns a new string and leaves its argument alone")]]
std::string upper_case(std::string text) {
    for (char& c : text) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
    return text;
}

int main() {
    std::string city = "Pune";
    is_leap_year(2028);           // the answer is computed and lost
    upper_case(city);             // a real bug: city is still "Pune"
    std::println("{}", city);

    city = upper_case(city);      // the fix: keep the result
    (void)is_leap_year(2028);     // a cast to void says "ignored on purpose"
    std::println("{}", city);
}
The warnings from both compilers Python · nodiscard_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra nodiscard.cpp -o nodiscard && ./nodiscard
g++-16 -std=c++23 -Wall -Wextra nodiscard.cpp -o nodiscard && ./nodiscard
""")

Output

$ clang++ -std=c++23 -Wall -Wextra nodiscard.cpp -o nodiscard && ./nodiscard
nodiscard.cpp:19:5: warning: ignoring return value of function declared with 'nodiscard' attribute [-Wunused-result]
   19 |     is_leap_year(2028);           // the answer is computed and lost
      |     ^~~~~~~~~~~~ ~~~~
nodiscard.cpp:20:5: warning: ignoring return value of function declared with 'nodiscard' attribute: it returns a new string and leaves its argument alone [-Wunused-result]
   20 |     upper_case(city);             // a real bug: city is still "Pune"
      |     ^~~~~~~~~~ ~~~~
2 warnings generated.
Pune
PUNE
$ g++-16 -std=c++23 -Wall -Wextra nodiscard.cpp -o nodiscard && ./nodiscard
nodiscard.cpp: In function 'int main()':
nodiscard.cpp:19:17: warning: ignoring return value of 'bool is_leap_year(int)', declared with attribute 'nodiscard' [-Wunused-result]
   19 |     is_leap_year(2028);           // the answer is computed and lost
      |     ~~~~~~~~~~~~^~~~~~
nodiscard.cpp:6:20: note: declared here
    6 | [[nodiscard]] bool is_leap_year(int year) {
      |                    ^~~~~~~~~~~~
nodiscard.cpp:20:15: warning: ignoring return value of 'std::string upper_case(std::string)', declared with attribute 'nodiscard': 'it returns a new string and leaves its argument alone' [-Wunused-result]
   20 |     upper_case(city);             // a real bug: city is still "Pune"
      |     ~~~~~~~~~~^~~~~~
nodiscard.cpp:12:13: note: declared here
   12 | std::string upper_case(std::string text) {
      |             ^~~~~~~~~~
Pune
PUNE

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 nodiscard_session.py

upper_case takes a copy of its argument, changes the copy and returns it, so upper_case(city); leaves city as it was, and the first line the program prints is Pune. Both compilers flag lines 19 and 20 with -Wunused-result, and both quote the reason written in the brackets, which C++20 allows. The fix is to use the result: city = upper_case(city);. To ignore a result on purpose, cast the call to void, as line 24 does: the cast tells the compiler and the reader that the result was thrown away deliberately.

The warning appears without -Wall, because the standard recommends that compilers warn here. The C++23 standard library marks functions such as empty() of std::string this way: name.empty(); only tests whether the string is empty, so a line that was meant to clear it gets a warning. Mark your own functions [[nodiscard]] when they return an error code, a new value or anything else whose loss is a bug, and leave it off functions such as std::printf, whose result, the number of characters printed, callers rarely need: the printing is the reason for the call.

Expecting a function to change its argument

upper_case(city); computes a new string from a copy and throws it away. Keep the result, city = upper_case(city);, or give the function a reference parameter if changing the caller’s string is the job.

Checkpoint

Check: [[nodiscard]] and ignored results

  1. Question 1 of 2 Which of the lines marked A to D makes the compiler warn?

    Read the code, then choose one answer.

    [[nodiscard]] int area(int width, int height) { return width * height; }
    
    int main() {
        int a = area(2, 3);       // A
        area(4, 5);               // B
        (void)area(6, 7);         // C
        return area(1, 1) - a;    // D
    }
    Show the answer: Question 1 of 2

    Answer: Only B

    The warning is for a call whose value is discarded. Storing the value, using it in an expression or returning it all count as using it; a cast to void says the loss is deliberate.

  2. Question 2 of 2 How do you call a [[nodiscard]] function and ignore its result on purpose, without a warning?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Cast the call to void: (void)is_leap_year(2028);

    [[nodiscard]] is a request for a warning, not a rule the program must obey. The cast to void, as on line 24 of nodiscard.cpp, marks the one call where losing the result is intended.

Running off the end of a non-void function is undefined behaviour

The C++ standard says that reaching the closing brace of a function that returns a value is undefined behaviour: What a program does when it breaks a rule for which the C++ standard sets no requirements, such as reading past the end of an array. The compiler may assume it never happens, so the result can change with the compiler and the optimisation level. More in “Object lifetime and undefined behaviour in C++”: the language makes no promise at all about what the program does next. Only main is exempt, and reaching the end of main means return 0;. Here is grade_points without its last line, so marks below 40 run off the end:

missing_return.cpp C++ · missing_return.cpp
// missing_return.cpp: grade_points minus its last line; marks below 40 run off the end.
#include <print>

int grade_points(int marks) {
    if (marks >= 90) return 10;
    if (marks >= 75) return 8;
    if (marks >= 60) return 6;
    if (marks >= 40) return 4;
}  // flowing off the end of a function that returns int: undefined behaviour

int main() {
    std::println("grade_points(95) = {}", grade_points(95));
    std::println("grade_points(30) = {}", grade_points(30));
}
One file, four builds, four behaviours Python · missing_return_session.py
from session import run

# The same file, built four ways. Both compilers warn, and each build still produces a program.
run("""
clang++ -std=c++23 -O0 missing_return.cpp -o clang_O0
clang++ -std=c++23 -O2 missing_return.cpp -o clang_O2 2> /dev/null
g++-16 -std=c++23 -O0 missing_return.cpp -o gcc_O0
g++-16 -std=c++23 -O2 missing_return.cpp -o gcc_O2 2> /dev/null
./clang_O0
./clang_O2
./gcc_O0
./gcc_O2
g++-16 -std=c++23 -Werror=return-type missing_return.cpp -o checked
""")

Output

$ clang++ -std=c++23 -O0 missing_return.cpp -o clang_O0
missing_return.cpp:9:1: warning: non-void function does not return a value in all control paths [-Wreturn-type]
    9 | }  // flowing off the end of a function that returns int: undefined behaviour
      | ^
1 warning generated.
$ clang++ -std=c++23 -O2 missing_return.cpp -o clang_O2 2> /dev/null
$ g++-16 -std=c++23 -O0 missing_return.cpp -o gcc_O0
missing_return.cpp: In function 'int grade_points(int)':
missing_return.cpp:9:1: warning: control reaches end of non-void function [-Wreturn-type]
    9 | }  // flowing off the end of a function that returns int: undefined behaviour
      | ^
$ g++-16 -std=c++23 -O2 missing_return.cpp -o gcc_O2 2> /dev/null
$ ./clang_O0
grade_points(95) = 10
grade_points(30) = 1
$ ./clang_O2
grade_points(95) = 10
grade_points(30) = 4
$ ./gcc_O0
[stopped by signal 5 (SIGTRAP): a shell reports exit status 133]
$ ./gcc_O2
[stopped by signal 10 (SIGBUS): a shell reports exit status 138]
$ g++-16 -std=c++23 -Werror=return-type missing_return.cpp -o checked
missing_return.cpp: In function 'int grade_points(int)':
missing_return.cpp:9:1: error: control reaches end of non-void function [-Werror=return-type]
    9 | }  // flowing off the end of a function that returns int: undefined behaviour
      | ^
cc1plus: some warnings being treated as errors
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_return_session.py

Both compilers warn even without -Wall: -Wreturn-type is on by default in C++ for Clang and GCC. The four programs were built anyway, and each did something different with grade_points(30):

Build What grade_points(30) did
Apple clang, -O0 Printed 1: whatever was left where the result goes
Apple clang, -O2 Printed 4: the test marks >= 40 was dropped
GCC, -O0 Stopped with SIGTRAP, a trap put there on purpose
GCC, -O2 Stopped with SIGBUS, after running past main

The optimisers may assume that undefined behaviour never happens. Apple clang at -O2 therefore treated the path without a return as impossible, concluded that marks >= 40 cannot fail, and gave every mark below 60 four points. GCC at -O0 and -Og turns code that must never run into a trap instruction (its -funreachable-traps option). GCC at -O2 worked out that grade_points(30) always runs off the end, so it deleted everything in main after the first std::println, the end of main included, and the program ran on into the bytes that followed.

The two GCC builds printed nothing, not even the line for grade_points(95): when output goes to a file or a pipe it waits in a buffer, and a program stopped by a signal never writes that buffer out. Shells such as bash and zsh report a program stopped by signal n as exit status: The number a program hands back when it ends, the value main returns. 0 means success; a shell keeps the last status in $?, and && runs the next command only after a status of 0. More in “How C++ compiles: preprocess, compile and link” 128 + n (POSIX promises only a value above 128), so 133 and 138 here. The last command of the session shows the cure: with -Werror=return-type the warning becomes an error, and no program is built.

Key idea

Every path through a function that returns a value must end in a return. Build with -Werror=return-type, so that a missing one stops the build instead of the program.

Checkpoint

Check: A missing return

  1. Question 1 of 2 A function int f(int n) reaches its closing brace without a return for some values of n. Which statements are true?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • -Werror=return-type turns the warning into an error, so no program is built.
    • Apple clang and GCC both warn about it without -Wall.
    • Calling it with such a value is undefined behaviour, even if the caller ignores the result.

    The warning is on by default; the danger is that a program is still built. Undefined behaviour let one file print 1, print 4, trap and crash, so make the warning an error.

  2. Question 2 of 2 A program is stopped by signal 5, SIGTRAP. Which exit status does a shell such as bash or zsh report for it?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 133

    bash and zsh report a program stopped by signal n as 128 + n, so 128 + 5 = 133. The GCC -O0 build of missing_return.cpp ended this way; the -O2 build was stopped by SIGBUS, signal 10 on macOS, and reported 138.

Small functions with one job are easier to change and test

Programs that do everything in main work until someone has to change them. This one prints an electricity bill for every meter reading on standard input, with a slab tariff invented for the lesson: the rate per unit rises after 100 and 200 units, a fixed charge rises above 300 units, and a duty of 5 % applies to both. Both versions read the same seven readings:

The same program before and after the split

One long main · bill_long.cpp

// bill_long.cpp: an electricity bill for every meter reading on standard input, all
// inside main. The tariff is invented for this lesson: 3.00 a unit up to 100 units,
// 4.50 a unit from 101 to 200, 6.00 a unit above 200; a fixed charge of 40.00 (75.00
// above 300 units); a 5 % duty on both.
#include <iostream>
#include <print>
#include <string>

int main() {
    std::println("{:<8}{:>7}{:>10}{:>8}{:>8}{:>10}", "Meter", "Units", "Energy", "Fixed", "Duty", "Total");
    std::string meter;
    int units = 0;
    int bills = 0;
    int rejected = 0;
    double billed = 0.0;
    double highest = 0.0;
    std::string highest_meter;
    while (std::cin >> meter >> units) {
        if (units < 0) {
            std::println("{:<8}{:>7}  rejected: a reading cannot be negative", meter, units);
            ++rejected;
            continue;
        }
        if (units > 100000) {
            std::println("{:<8}{:>7}  rejected: check the meter", meter, units);
            ++rejected;
            continue;
        }
        double energy = 0.0;
        int rest = units;
        if (rest > 200) {
            energy += (rest - 200) * 6.00;
            rest = 200;
        }
        if (rest > 100) {
            energy += (rest - 100) * 4.50;
            rest = 100;
        }
        energy += rest * 3.00;
        double fixed = 40.00;
        if (units > 300) {
            fixed = 75.00;
        }
        double duty = (energy + fixed) * 0.05;
        double total = energy + fixed + duty;
        std::println("{:<8}{:>7}{:>10.2f}{:>8.2f}{:>8.2f}{:>10.2f}", meter, units, energy, fixed, duty, total);
        ++bills;
        billed += total;
        if (total > highest) {
            highest = total;
            highest_meter = meter;
        }
    }
    std::println("{} bills, {} rejected, {:.2f} billed in all", bills, rejected, billed);
    if (bills > 0) {
        std::println("highest bill: {} ({:.2f})", highest_meter, highest);
    }
}

Input (standard input)

A-101 87
A-102 100
B-201 101
B-202 245
C-301 -12
C-302 412
D-401 250000

Output

Meter     Units    Energy   Fixed    Duty     Total
A-101        87    261.00   40.00   15.05    316.05
A-102       100    300.00   40.00   17.00    357.00
B-201       101    304.50   40.00   17.23    361.73
B-202       245   1020.00   40.00   53.00   1113.00
C-301       -12  rejected: a reading cannot be negative
C-302       412   2022.00   75.00  104.85   2201.85
D-401    250000  rejected: check the meter
5 bills, 2 rejected, 4349.62 billed in all
highest bill: C-302 (2201.85)

Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined bill_long.cpp -o bill_long && ./bill_long

Five functions · bill.cpp

// bill.cpp: the same program with five functions, each doing one job, and a short main.
#include <iostream>
#include <print>
#include <string>
#include <string_view>

struct Bill {
    double energy;
    double fixed;
    double duty;
    double total;
};

// The invented tariff of this lesson, one slab at a time.
double energy_charge(int units) {
    double charge = 0.0;
    if (units > 200) {
        charge += (units - 200) * 6.00;
        units = 200;
    }
    if (units > 100) {
        charge += (units - 100) * 4.50;
        units = 100;
    }
    return charge + units * 3.00;
}

double fixed_charge(int units) {
    return units > 300 ? 75.00 : 40.00;
}

Bill make_bill(int units) {
    double energy = energy_charge(units);
    double fixed = fixed_charge(units);
    double duty = (energy + fixed) * 0.05;
    return {energy, fixed, duty, energy + fixed + duty};
}

// Why a reading cannot be billed, or an empty string when it can.
std::string rejection_reason(int units) {
    if (units < 0) return "a reading cannot be negative";
    if (units > 100000) return "check the meter";
    return "";
}

void print_bill(std::string_view meter, int units, Bill bill) {
    std::println("{:<8}{:>7}{:>10.2f}{:>8.2f}{:>8.2f}{:>10.2f}", meter, units, bill.energy, bill.fixed, bill.duty, bill.total);
}

int main() {
    std::println("{:<8}{:>7}{:>10}{:>8}{:>8}{:>10}", "Meter", "Units", "Energy", "Fixed", "Duty", "Total");
    std::string meter, highest_meter;
    int units = 0, bills = 0, rejected = 0;
    double billed = 0.0, highest = 0.0;
    while (std::cin >> meter >> units) {
        std::string why = rejection_reason(units);
        if (!why.empty()) {
            std::println("{:<8}{:>7}  rejected: {}", meter, units, why);
            ++rejected;
            continue;
        }
        Bill bill = make_bill(units);
        print_bill(meter, units, bill);
        ++bills;
        billed += bill.total;
        if (bill.total > highest) {
            highest = bill.total;
            highest_meter = meter;
        }
    }
    std::println("{} bills, {} rejected, {:.2f} billed in all", bills, rejected, billed);
    if (bills > 0) std::println("highest bill: {} ({:.2f})", highest_meter, highest);
}

Input (standard input)

A-101 87
A-102 100
B-201 101
B-202 245
C-301 -12
C-302 412
D-401 250000

Output

Meter     Units    Energy   Fixed    Duty     Total
A-101        87    261.00   40.00   15.05    316.05
A-102       100    300.00   40.00   17.00    357.00
B-201       101    304.50   40.00   17.23    361.73
B-202       245   1020.00   40.00   53.00   1113.00
C-301       -12  rejected: a reading cannot be negative
C-302       412   2022.00   75.00  104.85   2201.85
D-401    250000  rejected: check the meter
5 bills, 2 rejected, 4349.62 billed in all
highest bill: C-302 (2201.85)

Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined bill.cpp -o bill && ./bill

readings.txt

A-101 87
A-102 100
B-201 101
B-202 245
C-301 -12
C-302 412
D-401 250000

The two versions printed the same ten lines, and so did their GCC builds. The split version is a few lines longer, and that is fine, because every piece now has a name. To find the pieces, look for the comments and blank lines in a long function: each commented block is a function waiting to be named after its comment. Step through one reading, B-201 with 101 units, on its way through the functions:

Interactive · step by step One meter reading through the functions of bill.cpp Step through the reading B-201, 101 units. Each box is a function; the arrows are calls.
One meter reading through the functions of bill.cpp, step 7 of 7. Showing main, rejection_reason (returns "": billable), make_bill (total 361.725), energy_charge (returns 304.50), fixed_charge (returns 40.00), print_bill. In focus: energy_charge, make_bill.mainreads B-201 101rejection_reasonreturns "": billablemake_billtotal 361.725energy_chargereturns 304.50fixed_chargereturns 40.00print_billone row of the table

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Read one line. The loop in main reads the meter B-201 and 101 units from standard input, then asks rejection_reason(101) whether the reading can be billed.
  2. Billable. 101 is neither negative nor above 100,000, so rejection_reason returns an empty string, and main goes on to make the bill.
  3. The energy charge. make_bill(101) calls energy_charge(101): the 1 unit above 100 costs 4.50 and the first 100 units 3.00 each, so it returns 304.50.
  4. The fixed charge. fixed_charge(101) returns 40.00, because 101 units is not above 300.
  5. Duty and total. make_bill adds 5 % duty on 344.50, which is 17.225, and returns a Bill of four amounts with the total 361.725. Nothing has been printed yet.
  6. One row printed. main passes the Bill to print_bill, which prints B-201, 101, 304.50, 40.00, 17.23 and 361.73: the amounts rounded to two decimals.
  7. Each part alone. Every box can now be called on its own: a test checks make_bill(101).total against 361.725 without typing any input, and a new tariff changes energy_charge only.

energy_charge holds the slab tariff and nothing else, so a change of rates touches one place. make_bill returns a Bill struct with all four amounts, so it can be checked without typing any input: a test can call it with 0, 100, 101, 200, 201 and 301 units, the edges of every slab. And main now reads like a summary of the program.

A few habits make functions easy to read and test: one job per function, a name that says what it returns or does (fixed_charge, print_bill), few parameters, and results returned rather than printed, so that the caller decides what to do with them.

Checkpoint

Check: Splitting a program into small functions

  1. Question 1 of 2 Using the tariff of energy_charge in bill.cpp (3.00 a unit up to 100 units, 4.50 from 101 to 200, 6.00 above 200), what does energy_charge(200) return?

    Type a number.

    A hint

    Two slabs are full and the third is empty.

    Show the answer: Question 1 of 2

    Answer: 750 (anything from 749.999 to 750.001 counts)

    The units above 200 cost nothing here, since there are none. Units 101 to 200 cost 100 × 4.50 = 450, and the first 100 units cost 100 × 3.00 = 300, so energy_charge(200) returns 750. Because the tariff lives in one function, a test can check this edge without typing any input.

  2. Question 2 of 2 void report() reads marks from standard input, adds them up and prints the total. Which split makes the adding testable?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A function that returns the total of a list of marks, called by code that reads the input and prints the result

    A function that takes its inputs as parameters and returns its result can be called by a test with any input. make_bill in bill.cpp is that kind of function: make_bill(101).total is 361.725 without any input typed.

Practise: three functions about primes

The exercise has three files: a header with the declarations, a source file where you write the definitions, and the public tests.

The exercise files

C header · exercise/primes.h

// primes.h: the three functions of the exercise. Keep these declarations as they are.
#pragma once

// True when n is prime: greater than 1, with no divisor other than 1 and itself.
[[nodiscard]] bool is_prime(int n);

// The smallest prime number greater than n (next_prime(13) is 17).
[[nodiscard]] int next_prime(int n);

// How many prime numbers are less than or equal to limit (count_primes_up_to(10) is 4).
[[nodiscard]] int count_primes_up_to(int limit);

primes.cpp (starter) · exercise/primes.cpp

// primes.cpp: your definitions. Each compiles, and gives wrong answers until you write it.
#include "primes.h"

bool is_prime(int n) {
    (void)n;  // remove this line when you use n
    return false;
}

int next_prime(int n) {
    return n + 1;
}

int count_primes_up_to(int limit) {
    (void)limit;
    return 0;
}

exercise/primes_test.cpp

// primes_test.cpp: the public tests. Build them together with your primes.cpp.
#include <cstdlib>
#include <print>
#include <string_view>

#include "primes.h"

int checks = 0;
int failures = 0;

void check(bool passed, std::string_view what) {
    ++checks;
    if (!passed) {
        ++failures;
        std::println("FAIL: {}", what);
    }
}

int main() {
    check(!is_prime(-7), "-7 is not prime");
    check(!is_prime(0), "0 is not prime");
    check(!is_prime(1), "1 is not prime");
    check(is_prime(2), "2 is prime");
    check(is_prime(3), "3 is prime");
    check(!is_prime(91), "91 is not prime (7 x 13)");
    check(is_prime(97), "97 is prime");
    check(!is_prime(1'000'000), "1000000 is not prime");
    check(is_prime(2'147'483'647), "2147483647 is prime");
    check(next_prime(-5) == 2, "next_prime(-5) is 2");
    check(next_prime(2) == 3, "next_prime(2) is 3");
    check(next_prime(13) == 17, "next_prime(13) is 17");
    check(next_prime(89) == 97, "next_prime(89) is 97");
    check(count_primes_up_to(1) == 0, "no primes up to 1");
    check(count_primes_up_to(10) == 4, "4 primes up to 10");
    check(count_primes_up_to(100) == 25, "25 primes up to 100");
    check(count_primes_up_to(100'000) == 9'592, "9592 primes up to 100000");
    if (failures == 0) {
        std::println("All {} checks passed", checks);
        return EXIT_SUCCESS;
    }
    std::println("{} of {} checks failed", failures, checks);
    return EXIT_FAILURE;
}
  1. Save the three files in one folder and build the tests with the starter file. They fail, as the session below shows: 10 of the 17 checks fail before you have written anything.
  2. Write is_prime, then next_prime and count_primes_up_to, both of which can call is_prime. Rebuild with clang++ -std=c++23 -Wall -Wextra primes.cpp primes_test.cpp -o primes_test (or g++-16) until it prints All 17 checks passed.
  3. Build once more with -fsanitize=undefined added and run the tests again: no runtime error line may appear.
The tests with the starter file Python · primes_session.py
from session import run

# The tests built with the starter file: every function still gives a wrong answer.
run("""
cd exercise && clang++ -std=c++23 -Wall -Wextra primes.cpp primes_test.cpp -o primes_test && ./primes_test
""")

Output

$ cd exercise && clang++ -std=c++23 -Wall -Wextra primes.cpp primes_test.cpp -o primes_test && ./primes_test
FAIL: 2 is prime
FAIL: 3 is prime
FAIL: 97 is prime
FAIL: 2147483647 is prime
FAIL: next_prime(-5) is 2
FAIL: next_prime(13) is 17
FAIL: next_prime(89) is 97
FAIL: 4 primes up to 10
FAIL: 25 primes up to 100
FAIL: 9592 primes up to 100000
10 of 17 checks failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 primes_session.py

A first hint: use trial division: Testing whether a number n is prime by dividing it by 2, 3, 4 and so on. A divisor larger than the square root of n pairs with one below it, so the test can stop once d * d is greater than n. More in “C++ practice set: loops, conditions and numbers”, testing divisors d from 2 while d <= n / d. The obvious condition d * d <= n overflows int on the check for 2,147,483,647, the largest int on these compilers, because 46,341 × 46,341 is larger than that, and signed overflow is undefined behaviour too. The sanitizer build of step 3 catches it.

Prime Number Checker & Generator Check what your is_prime and next_prime return for numbers of your own, with the smallest divisor shown.

Once is_prime and its two partners pass, try the questions below on calls, declarations, returned structs, warnings and small functions. They adapt to your answers, opening with whichever of the six ideas has tripped you up most, and a wrong choice names the slip behind it:

Practice that adapts to you

Practise this lesson

13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 13 In int grade_points(int marks), what is marks?

    Choose one answer.

    Show the answer: Question 1 of 13

    Answer: A parameter: a variable of the function, created at each call and initialised from the argument

    Parameters are the function's own variables for its inputs. A call evaluates each argument and initialises the matching parameter with it.

  2. Question 2 of 13 What does this program print?

    Read the code, then choose one answer.

    #include <print>
    
    int clamp_marks(int marks) {
        if (marks < 0) return 0;
        if (marks > 100) return 100;
        return marks;
    }
    
    int main() {
        std::println("{} {} {}", clamp_marks(-5), clamp_marks(73), clamp_marks(120));
    }
    Show the answer: Question 2 of 13

    Answer: 0 73 100

    A return ends the function at once and hands back its value, so the tests read like a list of cases, as in grade_points.

  3. Question 3 of 13 main calls area(3), and area is defined below main. Which change makes the file compile without moving any function?

    Choose one answer.

    Show the answer: Question 3 of 13

    Answer: Add int area(int side); above main

    The compiler reads from the top and never looks ahead. A declaration ends with a semicolon and can be repeated; the definition, with the body, appears once.

  4. Question 4 of 13 The exercise's primes.h holds [[nodiscard]] bool is_prime(int n); and primes_test.cpp includes it. Where must the body of is_prime be?

    Choose one answer.

    Show the answer: Question 4 of 13

    Answer: In exactly one .cpp file of the program, here primes.cpp

    Declarations go in headers so every caller can see them; each ordinary function is defined once, in one source file, which is how the exercise is laid out.

  5. Question 5 of 13 This uses split_minutes from split_out_params.cpp. What does it print, and why would the struct version not have this problem?

    Read the code, then choose one answer.

    int hours;
    int minutes;
    split_minutes(155, minutes, hours);
    std::println("{} h {} min", hours, minutes);
    Show the answer: Question 5 of 13

    Answer: 35 h 2 min: the output parameters were passed in the wrong order, and nothing at the call says which is which

    With a returned struct the results come back with their names, film.hours and film.minutes, so they cannot be swapped by the order of arguments.

  6. Question 6 of 13 Duration split_minutes(int total_minutes) returns a struct with hours and minutes. Which line names both parts of the result in one declaration?

    Choose one answer.

    Show the answer: Question 6 of 13

    Answer: auto [hours, minutes] = split_minutes(1000);

    split_struct.cpp printed 1000 minutes is 16 h 40 min with exactly this line.

  7. Question 7 of 13 Where is [[nodiscard]] written so that ignoring the result of is_leap_year makes the compiler warn?

    Choose one answer.

    Show the answer: Question 7 of 13

    Answer: Before the return type: [[nodiscard]] bool is_leap_year(int year);

    The attribute goes on the declaration, and with a header that is the declaration in the header, so every caller's compiler sees it.

  8. Question 8 of 13 Which of these functions should be marked [[nodiscard]]?

    Choose every answer that is right.

    Show the answer: Question 8 of 13

    Answer:

    • int save_file(std::string_view path), which returns an error code
    • bool is_valid_pin_code(std::string_view text)
    • std::string trimmed(std::string text)

    Mark functions whose result is the reason for the call or reports a failure. Functions called for their effect stay unmarked, or every caller would need a cast.

  9. Question 9 of 13 The Apple clang -O2 build of missing_return.cpp printed 4 for grade_points(30). Why 4?

    Choose one answer.

    Show the answer: Question 9 of 13

    Answer: The optimiser assumed the path without a return never runs, so it treated marks >= 40 as always true

    Optimisers rearrange code on the assumption that undefined behaviour never happens. Here that assumption turned a missing return into a wrong grade with no crash, the hardest kind of bug to notice.

  10. Question 10 of 13 Which flag makes a missing return stop the build instead of producing a program?

    Choose one answer.

    Show the answer: Question 10 of 13

    Answer: -Werror=return-type

    A warning reports the problem and the build goes on; -Werror=return-type (or -Werror for every warning) makes it an error, so the bug cannot reach a program.

  11. Question 11 of 13 make_bill(245) uses energy 1020.00 and fixed 40.00. Duty is 5 % of their sum. What total does it return?

    Type a number.

    A hint

    Add the two charges first, then add 5 % of that sum.

    Show the answer: Question 11 of 13

    Answer: 1113 (anything from 1112.995 to 1113.005 counts)

    energy + fixed is 1060.00, the duty is 1060.00 × 0.05 = 53.00, and the total is 1113.00, the amount the program printed for meter B-202.

  12. Question 12 of 13 In a long main, what is a good sign of a function waiting to be pulled out?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: A block of lines under a comment that names one step, such as // fixed charge

    bill_long.cpp had a commented block for each step of a bill. Each became a function named after its comment: energy_charge, fixed_charge, make_bill, print_bill.

  13. Question 13 of 13 You test energy_charge after a change of tariff. Which unit counts test the edges of its slabs?

    Choose every answer that is right.

    Show the answer: Question 13 of 13

    Answer:

    • 100 and 101
    • 0
    • 200 and 201

    Bugs in slab tariffs sit at the edges, where one rate stops and the next starts. Because energy_charge is a function of its own, each edge is a one-line test.

Interview questions

Fresher · What does [[nodiscard]] do, and how do you ignore such a result on purpose?

[[nodiscard]] on a function, or on a type, asks the compiler to warn whenever the result of a call is thrown away, as in upper_case(city);, which computes a new string and loses it. Apple clang and GCC both warn with -Wunused-result, even without -Wall, and since C++20 a reason in brackets, [[nodiscard("...")]], becomes part of the warning. The attribute changes no generated code; it only adds the warning, which -Werror turns into an error. Use it for functions whose result is the whole point: error codes, new values, tests such as empty(). To discard a result deliberately, cast the call to void: (void)is_leap_year(2028);.

A good answer usually earns a follow-up about types and headers. Try these two before you look at the reasons behind each choice:

Checkpoint

Interview round: two follow-up questions

  1. Question 1 of 2 A follow-up: what does [[nodiscard]] do when it is written on a type, as in struct [[nodiscard]] ErrorCode { int value; };?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Every call of a function that returns an ErrorCode warns when its result is ignored, without marking each function

    Putting the attribute on an error type protects every function that returns it, including ones written later, which is why libraries often mark their error and result types this way.

  2. Question 2 of 2 A second follow-up: a header defines the ordinary function int area(int side) { return side * side; }, and two .cpp files include it. What goes wrong?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Each .cpp file now holds a definition of area, so linking the two object files fails with a duplicate symbol

    Headers hold declarations because every file that calls a function needs one, while the one-definition rule allows an ordinary function a single definition. The module's lesson on headers shows the error and the fixes.

Key takeaways

  • A function has a return type, a name, a parameter list and a body; a call copies each argument into its parameter, runs the body until a return, and takes the returned value.
  • The compiler must see a declaration before a call. Declare as often as you need, and define each ordinary function exactly once.
  • Return results instead of writing into output parameters, and return a struct when there are several.
  • [[nodiscard]] makes both compilers warn when a result is ignored; cast the call to void to ignore it on purpose.
  • Reaching the end of a non-void function other than main is undefined behaviour: one file printed 1, printed 4, trapped and crashed, so build with -Werror=return-type.
  • Short functions with one job and a clear name make a program easier to change, and their results can be tested without running the whole program.

Terms in this lesson

argument
The value a call passes to a function, such as maths in grade_points(maths). It is evaluated before the call starts and initialises the matching parameter.
function declaration
A function's return type, name and parameter types ended by a semicolon, as in int grade_points(int marks);. It lets code call the function before, or without, seeing its body.
output parameter
A reference or pointer parameter that a function writes a result into, so the caller must create the variable before the call. Returning a value, or a struct of several values, is usually clearer.
parameter
A variable named in a function's parameter list, such as marks in int grade_points(int marks). Each call creates it anew and initialises it from the argument the call passes.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.