2. Week 1 Thursday: The Box Model and Syntax Errors
≪ 1. Week 1 Tuesday: Getting Started | Table of Contents | 3. Week 2 Tuesday: Arithmetic, Integers, and Doubles ≫In the preceeding lecture, you have been introduced to integer variables and some basic arithmetic operations involving them, in addition to being able to have programs output things to the screen. Today, our goals are twofold:
- establish a system that represents how C++ interprets code accurately, and
- understand some common compilation errors and the error messages that come with them.
Ideally, having a good model of how C++ understands code will let us understand why these compilation errors occur without resorting to rote memorisation of each error.
The Box Model of C++
Let’s consider the following program:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int a = 7;
6 int b = 19;
7 cout << "The fourth prime number is " << a
8 << " and the eighth prime number is " << b
9 << endl;
10
11 a = 1;
12 cout << "a was changed to " << a << endl;
13 return 0;
14}
For now, we’ll focus on everything between the lines
int main() { and return 0;.
Everything else must be present for the program to compile,
but it’s okay if you don’t know what they do.
The middle few lines are the ones
that are pertinent to the program we end up creating.
Let’s model your C++ program as a person (I’m naming him Cecil) managing a very large warehouse full of boxes. (Draw a person in front of a very large warehouse.) We are collectively Cecil’s boss, and each line of code is a command that Cecil is compelled to obey. In fact Cecil is a lot like the horse from Animal Farm.
First, int a = 7; tells Cecil to create a new int variable.
Cecil will grab an integer-sized box,
label it with a so he can find it later,
and put the number 7 inside it.
Likewise, int b = 19; tells Cecil
to grab another integer-sized box,
label it with b so he can find it later,
and put the number 19 inside it.
The next few lines cout << ... instruct Cecil to write a report
and send it to our screen.
cout means “send report”,
and each thing in between the arrows <<
is something Cecil needs to put in the report.
Sometimes it’s text, and sometimes it’s the contents of a box.
When Cecil sees ... << a << ...,
he will look at whatever’s in box a and write down its contents.
Later, when we say a = 1,
Cecil retrieves box a
and replaces its contents with the number 1.
So his second report will say a is now 1
instead of a is now 7.
That’s that!
Common Errors
Let us now introduce some vocabulary: declaration is when Cecil grabs a box for the first time and puts a label on it; initialisation is when Cecil puts something in said box for the first time. In the above example, these two steps happened at the same time: Cecil grabbed a box, labelled it, and immediately filled it. We can also split this into two commands:
There aren’t many cases where one actually wants to do this.
But this leads us to a couple of common errors one can make.
Suppose one forgets the a = 7 part and writes:
This tells Cecil to grab an integer-sized box and label it a,
then immediately tell us what’s in the box.
This doesn’t actually result in an error:
the same boxes are reused over and over again by different programs,
and Cecil will just report the value
of whatever garbage was left in whichever box he grabbed.
While it’s not a compilation error or a runtime error,
this constitutes something called undefined behaviour,
as we cannot reliably predict what Cecil will report
as the value of the variable a.
(In fact, you may get a different output
every single time you run the program.)
On the other hand, consider what Cecil is faced with when we skip the declaration step.
1#include <iostream>
2using namespace std;
3
4int main() {
5 a = 7;
6 cout << "a contains the value " << a << endl;
7
8 return 0;
9}
The command a = 7
tells Cecil to grab the box labelled a
and replace its contents with 7.
But Cecil doesn’t have any boxes,
and he will have a breakdown and cry.
Specifically, he will say something like
main.cpp: In function ‘int main()’:
main.cpp:5:5: error: ‘a’ was not declared in this scope
5 | a = 7;
| ^
This is an example of a build error,
and the above is the compiler’s error message!
main.cpp indicates the code file in which the error occured.
On the second line, main:5:5: ...,
the 5 indicates the line number and the 5 indicates
which character in that line the error occured in.
(The line is reproduced as well.)
Following this,
the message ‘i’ was not declared in this scope indicates
that we have referenced a variable before declaring it.
In your IDE, the precise format of the error message may be slightly different, but you will always see the exact location of the error and a wordy description of what the compiler thinks of your code.
Some other error messages can be a bit trickier to parse. Consider the following program:
1#include <iostream>
2using namespace std;
3
4int main() {
5 int a = 7;
6 cout << "a contains the value " << a < endl;
7
8 return 0;
9}
We have now fixed the undeclared variable error, but there’s been another syntax error introduced. Perhaps you can eyeball it, but let’s see how the compiler describes the issue.
Error Message (Open at your Own Risk)
main.cpp: In function ‘int main()’:
main.cpp:6:42: error: no match for ‘operator<’ (operand types are ‘std::basic_ostream<char>’ and ‘<unresolved overloaded function type>’)
6 | cout << "a contains the value " << a < endl;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
main.cpp:6:42: note: there are 14 candidates
In file included from /usr/include/c++/15.2.1/string:50,
from /usr/include/c++/15.2.1/bits/locale_classes.h:42,
from /usr/include/c++/15.2.1/bits/ios_base.h:43,
from /usr/include/c++/15.2.1/ios:46,
from /usr/include/c++/15.2.1/bits/ostream.h:43,
from /usr/include/c++/15.2.1/ostream:42,
from /usr/include/c++/15.2.1/iostream:43,
from main.cpp:1:
/usr/include/c++/15.2.1/bits/stl_iterator.h:450:5: note: candidate 1: ‘template<class _Iterator> constexpr bool std::operator<(const reverse_iterator<_Iterator>&, const reverse_iterator<_Iterator>&)’
450 | operator<(const reverse_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:450:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::reverse_iterator<_Iterator>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:495:5: note: candidate 2: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator<(const reverse_iterator<_Iterator>&, const reverse_iterator<_IteratorR>&)’
495 | operator<(const reverse_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:495:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::reverse_iterator<_Iterator>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1689:5: note: candidate 3: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator<(const move_iterator<_IteratorL>&, const move_iterator<_IteratorR>&)’
1689 | operator<(const move_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1689:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::move_iterator<_IteratorL>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1755:5: note: candidate 4: ‘template<class _Iterator> constexpr bool std::operator<(const move_iterator<_IteratorL>&, const move_iterator<_IteratorL>&)’
1755 | operator<(const move_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_iterator.h:1755:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::move_iterator<_IteratorL>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/stl_algobase.h:64,
from /usr/include/c++/15.2.1/string:53:
/usr/include/c++/15.2.1/bits/stl_pair.h:1073:5: note: candidate 5: ‘template<class _T1, class _T2> constexpr bool std::operator<(const pair<_T1, _T2>&, const pair<_T1, _T2>&)’
1073 | operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/stl_pair.h:1073:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::pair<_T1, _T2>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/basic_string.h:51,
from /usr/include/c++/15.2.1/string:56:
/usr/include/c++/15.2.1/string_view:677:5: note: candidate 6: ‘template<class _CharT, class _Traits> constexpr bool std::operator<(basic_string_view<_CharT, _Traits>, basic_string_view<_CharT, _Traits>)’
677 | operator< (basic_string_view<_CharT, _Traits> __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/string_view:677:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘std::basic_string_view<_CharT, _Traits>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/string_view:684:5: note: candidate 7: ‘template<class _CharT, class _Traits> constexpr bool std::operator<(basic_string_view<_CharT, _Traits>, __type_identity_t<basic_string_view<_CharT, _Traits> >)’
684 | operator< (basic_string_view<_CharT, _Traits> __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/string_view:684:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘std::basic_string_view<_CharT, _Traits>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/string_view:692:5: note: candidate 8: ‘template<class _CharT, class _Traits> constexpr bool std::operator<(__type_identity_t<basic_string_view<_CharT, _Traits> >, basic_string_view<_CharT, _Traits>)’
692 | operator< (__type_identity_t<basic_string_view<_CharT, _Traits>> __x,
| ^~~~~~~~
/usr/include/c++/15.2.1/string_view:692:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: couldn’t deduce template parameter ‘_CharT’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4164:5: note: candidate 9: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const __cxx11::basic_string<_CharT, _Traits, _Alloc>&, const __cxx11::basic_string<_CharT, _Traits, _Alloc>&)’
4164 | operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4164:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4178:5: note: candidate 10: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const __cxx11::basic_string<_CharT, _Traits, _Alloc>&, const _CharT*)’
4178 | operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4178:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4191:5: note: candidate 11: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const _CharT*, const __cxx11::basic_string<_CharT, _Traits, _Alloc>&)’
4191 | operator<(const _CharT* __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/bits/basic_string.h:4191:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: mismatched types ‘const _CharT*’ and ‘std::basic_ostream<char>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/memory_resource.h:49,
from /usr/include/c++/15.2.1/string:72:
/usr/include/c++/15.2.1/tuple:2624:5: note: candidate 12: ‘template<class ... _TElements, class ... _UElements> constexpr bool std::operator<(const tuple<_Elements ...>&, const tuple<_Elements ...>&)’
2624 | operator<(const tuple<_TElements...>& __t,
| ^~~~~~~~
/usr/include/c++/15.2.1/tuple:2624:5: note: template argument deduction/substitution failed:
main.cpp:6:44: note: ‘std::basic_ostream<char>’ is not derived from ‘const std::tuple<_Elements ...>’
6 | cout << "a contains the value " << a < endl;
| ^~~~
In file included from /usr/include/c++/15.2.1/bits/ios_base.h:48:
/usr/include/c++/15.2.1/system_error:326:3: note: candidate 13: ‘bool std::operator<(const error_code&, const error_code&)’
326 | operator<(const error_code& __lhs, const error_code& __rhs) noexcept
| ^~~~~~~~
/usr/include/c++/15.2.1/system_error:326:31: note: no known conversion for argument 1 from ‘std::basic_ostream<char>’ to ‘const std::error_code&’
326 | operator<(const error_code& __lhs, const error_code& __rhs) noexcept
| ~~~~~~~~~~~~~~~~~~^~~~~
/usr/include/c++/15.2.1/system_error:509:3: note: candidate 14: ‘bool std::operator<(const error_condition&, const error_condition&)’
509 | operator<(const error_condition& __lhs,
| ^~~~~~~~
/usr/include/c++/15.2.1/system_error:509:36: note: no known conversion for argument 1 from ‘std::basic_ostream<char>’ to ‘const std::error_condition&’
509 | operator<(const error_condition& __lhs,
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~
This is an overwhelmingly massive error message, but still the most pertinent information is in those first few lines.
main.cpp: In function ‘int main()’:
main.cpp:6:42: error: no match for ‘operator<’ (operand types are ‘std
::basic_ostream<char>’ and ‘<unresolved overloaded function type>’)
6 | cout << "a contains the value " << a < endl;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
The specific character the compiler couldn’t interpret
was kindly pointed out in line 6, character 42.
We can now tell that it should be << instead of <.
(The remainder of the error message
is telling us all the different ways
C++ could have interpreted < in any context
and why none of them are sensible here.)
Here are some practise problems all about understanding some common errors one may run into early on. There are two goals:
- You should be able to see what the error is and how it differs from code that compiles.
- You should be able to read and interpret the relevant parts of the compiler’s error message. You don’t have to read all of it, you just have to extract enough information to determine what the error was.
You may be inclined to believe that reading the error message is not a useful skill when one can visually spot the error. However, if a typo is made in a project spanning millions of lines of code, you are almost surely not going to find it by reading all the code yourself. The error messages are supposed to help!
Finally, some of these are really tricky. Please feel free to look things up on the internet if you are feeling stuck! Running into mysterious mistakes is a very common experience, and I would like you to be familiar enough with looking up error messages sooner rather than later.
Problem 1.
Problem 2.
1#include <isotream>
2using namespace std;
3
4int main() {
5 // converts the quantity "461 minutes" into a better
6 // format.
7 int minutes = 461;
8 cout << minutes << " minutes." << endl;
9
10 int hours = 7;
11 int minutes = 461 - 7 * 60;
12 cout << "That's also " << hours << " hours and "
13 << minutes << " minutes." << endl;
14 return 0;
15}
Problem 3.
Problem 4.