05.basic Concepts III
05.basic Concepts III
Programming
5. Basic Concepts III
Entities and Control Flow
Federico Busato
2025-01-22
Table of Contents
1 Entities
3 Enumerators
5 Control Flow
if Statement
for and while Loops
Range-based for Loop
switch
goto
Avoid Unused Variable Warning
2/65
Table of Contents
6 Namespace
Explicit Global Namespace
Namespace Alias
using-Declaration
using namespace-Directive
inline Namespace ⋆
3/65
Table of Contents
⋆
7 Attributes
[[nodiscard]]
[[maybe unused]]
[[deprecated]]
[[noreturn]]
4/65
Entities
Entities
A C++ program is set of language-specific keywords (for, if, new, true, etc.),
identifiers (symbols for variables, functions, structures, namespaces, etc.), expressions
defined as sequence of operators, and literals (constant value tokens)
C++ Entity
An entity is a value, object, reference, function, enumerator, type, class member, or
template
Identifiers and user-defined operators are the names used to refer to entities
Entities also captures the result(s) of an expression
Preprocessor macros are not C++ entities
5/65
Declaration and
Definition
Declaration/Definition
Declaration/Prototype
A declaration (or prototype) introduces an entity with an identifier describing its
type and properties
A declaration is what the compiler and the linker needs to accept references (usage) to
that identifier
Entities can be declared multiple times. All declarations are the same
Definition/Implementation
An entity definition is the implementation of a declaration. It defines the properties
and the behavior of the entity
7/65
Declaration/Definition struct Example
struct A; // declaration 1
struct A; // declaration 2 (ok)
struct A { // definition
char c;
}
8/65
Enumerators
Enumerator - enum
Enumerator
An enumerator enum is a data type that groups a set of named integral constants
enum color_t { BLACK, BLUE, GREEN };
The problem:
enum color_t { BLACK, BLUE, GREEN };
enum fruit_t { APPLE, CHERRY };
// bool b = (color == fruit) compile error we are trying to match colors with fruits
// BUT, they are different things entirely
// int a1 = Color::GREEN; compile error
// int a2 = Color::RED + Color::GREEN; compile error
int a3 = (int) Color::GREEN; // ok, explicit conversion
10/65
enum/enum class Features
12/65
enum class Features - C++20
• C++20 allows introducing the enumerator identifiers into the local scope to
decrease the verbosity
enum class Color { RED, GREEN, BLUE };
switch (x) {
using enum Color; // C++20
case RED:
case GREEN:
case BLUE:
}
14/65
enum/enum class and constexpr⋆
• C++17 constexpr expressions don’t allow out-of-range values for (only) enum
without explicit underlying type
enum Color { RED };
enum Fruit : int { APPLE };
enum class Device { PC };
17/65
Anonymous and Unnamed struct⋆
The C++ standard allows unnamed struct but, contrary to C, does not allow
anonymous struct (i.e. without a name)
struct {
int x;
} my_struct; // unnamed struct, ok
struct S {
int x;
struct { int y; }; // anonymous struct, compiler warning with -Wpedantic
}; // -Wpedantic: diagnose use of non-strict ISO C++ extensions
18/65
Bitfield
Bitfield
A bitfield is a variable of a structure with a predefined bit width. A bitfield can hold
bits instead bytes
struct S1 {
int b1 : 10; // range [0, 1023]
int b2 : 10; // range [0, 1023]
int b3 : 8; // range [0, 255]
}; // sizeof(S1): 4 bytes
struct S2 {
int b1 : 10;
int : 0; // reset: force the next field
int b2 : 10; // to start at bit 32
}; // sizeof(S2): 8 bytes
19/65
union 1/2
Union
A union is a special data type that allows to store different data types in the same
memory location
• The union is only as big as necessary to hold its largest data member
• The union is a kind of “overlapping” storage
20/65
union 2/2
union A {
int x;
char y;
}; // sizeof(A): 4
A a;
a.x = 1023; // bits: 00..000001111111111
a.y = 0; // bits: 00..000001100000000
cout << a.x; // print 512 + 256 = 768
NOTE: Little-Endian encoding maps the bytes of a value in memory in the reverse order. y
maps to the last byte of x
The if statement executes the first branch if the specified condition is evaluated to
true , the second branch otherwise
• Short-circuiting:
if (<true expression> r| array[-1] == 0)
... // no error!! even though index is -1
// left-to-right evaluation
• Ternary operator :
<cond> ? <expression1> : <expression2>
<expression1> and <expression2> must return a value of the same or convertible
type
int value = (a == b) ? a : (b == c ? b : 3); // nested
22/65
for and while Loops
• for
for ([init]; [cond]; [increment]) {
...
}
• while
while (cond) {
...
}
• do while
do {
...
} while (cond);
To use when number of iterations is not known, but there is at least one iteration 23/65
for Loop Features and Jump Statements
• Infinite loop:
for (;;) // also while(true);
...
C++11 introduces the range-based for loop to simplify the verbosity of traditional
for loop constructs. They are equivalent to the for loop operating over a range of
values, but safer
The range-based for loop avoids the user to specify start, end, and increment of the
loop
for (int v : { 3, 2, 1 }) // INITIALIZER LIST
cout << v << " "; // print: 3 2 1
int values[] = { 3, 2, 1 };
for (int v : values) // ARRAY OF VALUES
cout << v << " "; // print: 3 2 1
27/65
switch 1/2
The switch statement evaluates an expression ( int , char , enum class , enum )
and executes the statement associated with the matching case value
char x = ...
switch (x) {
case 'a': y = 1; break;
default: return -1;
}
return y;
Switch scope:
int x = 1;
switch (1) {
case 0: int x; // nearest scope
case 1: cout << x; // undefined!!
case 2: { int y; } // ok
// case 3: cout << y; // compile error 28/65
}
switch 2/2
Fall-through:
MyEnum x
int y = 0;
switch (x) {
case MyEnum::A: // fall-through
case MyEnum::B: // fall-through
case MyEnum::C: return 0;
default: return -1;
}
Control flow with initializing statement aims at simplifying complex actions before
the condition evaluation and restrict the scope of a variable which is visible only in the
control flow body
C++17 introduces if statement with initializer
if (int ret = x + y; ret < 10)
cout << ret;
become:
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (<condition>)
goto LABEL;
}
}
31/65
LABEL: ;
goto 2/4
Best solution:
bool my_function(int M, int M) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (<condition>)
return false;
}
}
return true;
}
32/65
goto 3/4
33/65
goto 4/4
34/65
Avoid Unused Variable Warning 1/3
Most compilers issue a warning when a variable is unused. There are different
situations where a variable is expected to be unused
// EXAMPLE 1: macro dependency
int f(int value) {
int x = value;
# if defined(ENABLE_SQUARE_PATH)
return x * x;
# else
return 0;
# endif
}
35/65
Avoid Unused Variable Warning⋆ 2/3
There are different ways to solve the problem depending on the standard used
37/65
Namespace
Overview
The problem: Named entities, such as variables, functions, and compound types declared
outside any block has global scope, meaning that its name/symbol is valid anywhere in
the code
Namespaces allow grouping named entities that otherwise would have global
scope into narrower scopes, giving them namespace scope
Namespaces provide a method for preventing name conflicts in large projects. Symbols
declared inside a namespace block are placed in a named scope that prevents them from
being mistaken for symbols with identical names
38/65
Namespace Syntax
namespace [<name>] {
} // namespace <name>
39/65
Namespace Example 1
# include <iostream>
namespace my_namespace1 {
void f() {
std::cout << "my_namespace1" << std::endl;
}
} // namespace my_namespace1
namespace my_namespace2 {
void f() {
std::cout << "my_namespace2" << std::endl;
}
} // namespace my_namespace2
int main () {
my_namespace1::f(); // print "my_namespace1"
my_namespace2::f(); // print "my_namespace2"
// f(); // compile error f() is not visible 40/65
}
Namespace - Alternative Syntax
# include <iostream>
namespace my_namespace1 {}
int main () {
my_namespace1::f(); // print "my_namespace1"
}
41/65
Special Namespaces
• All functionalities and data types provided with the standard library (distributed
along with the compiler) are declared within the std namespace
• The global namespace can be specified with ::identifier and can be useful to
prevent conflicts with surrounding namespaces
42/65
Nested Namespaces
namespace my_namespace1 {
void f() { cout << "my_namespace1::f()"; }
namespace my_namespace2 {
} // namespace my_namespace2
} // namespace my_namespace1
my_namespace1::my_namespace2::f();
The explicit global namespace syntax ::identifier can be useful to prevent conflicts
with surrounding namespaces
namespace my_namespace {
void g() {
f(); // print "my_namespace::f()"
::f(); // print "global::f()"
}
} // namespace my_namespace
44/65
Namespace Alias
namespace very_long_namespace {
namespace even_longer {
void g() {}
} // namespace even_longer
} // namespace very_long_namespace
int main() {
namespace ns2 = very_long_namespace::even_longer; // namespace alias
// available only in this scope
ns1::g();
ns2::g();
}
45/65
using-Declaration 1/2
The using -declaration introduces a specific name/system from a namespace into the
current scope. This is useful for improving code readability and reducing verbosity
namespace <name> {
<identifier>
}
using <name>::<identifier>;
<identifier>;
46/65
using-Declaration 2/2
namespace my_namespace {
struct S {};
using T = int;
} // namespace my_namespace
using my_namespace::f;
using my_namespace::S;
using my_namespace::T;
f(); // print "my_namespace::f()"
S s;
T x;
47/65
// struct S {}; // compile error "struct S" already defined by my_namespace::T
using namespace-Directive
The using namespace -directive introduces all the identifiers in a scope without
having to specify them explicitly with the namespace name
Similarly to using -declaration, it is useful for improving code readability and reducing
verbosity. On the other hand, it could make the code bug-prone because of the
complex name lookup rules, especially if coupled with function overloadding
It is generally recommended not to write using namespace , especially at the global
level. Otherwise, it defeats the purpose of the namespace
48/65
using namespace-Directive
namespace my_namespace {
struct S {};
} // namespace my_namespace
int main () {
using namespace my_namespace;
f(); // print "my_namespace::f()"
S s;
}
49/65
using namespace-Directive vs. using-declaration
namespace A { int x = 0; }
namespace B {
int y = 3;
int x = 7;
}
int main () {
using namespace A;
int x = 3; // ok!! even if it is already defined in my_namespace
using B::y;
// int y = 5; // compiler error!! "y" is already defined in this scope
}
void f() {
using B::x;
using namespace A;
cout << x; // print 7, B::x has higher priority 50/65
}
using namespace-Directive Transitive Property 1/3
using namespace -directive has the transitive property for its identifiers when used
into another namespace
namespace A {
void f() { cout << "A::f()"; }
}
namespace B {
using namespace A;
}
int main() {
using namespace B;
f(); // ok, print "A::f()"
}
51/65
⋆
using namespace-Directive Transitive Property 2/3
The unqualified name lookup is the mechanism by which the compiler searches for
the declaration of an identifier without using any explicit scope qualifiers like the ::
operator
52/65
⋆
using namespace-Directive Transitive Property 3/3
namespace A { int i = 0; }
namespace C {
int i = 3;
namespace B {
using namespace A; // unqualified name lookup of A within B:
int x = i; // it is the nearest enclosing namespace which contains
} // namespace B // both A and B -> global namespace
// "int x = i" -> "int x = C::i" because C has higher
} // namespace C // precedence than the global namespace
int main() {
using namespace B;
cout << C::B::x; // print "3"
} 53/65
⋆
inline Namespace
namespace my_namespace1 {
} // namespace my_namespace1
In addtion, C++11 and later add standard attributes such as maybe unused ,
deprecated , and nodiscard 55/65
[[nodiscard]] Attribute 1/3
MyStruct g() {
MyStruct s;
return s;
}
MyStruct g() {
[[nodiscard]] MyStruct() {}
[[nodiscard]] MyStruct(const MyStruct&) {}
}
58/65
[[maybe unused]] Attribute 1/2
59/65
The limits of [[maybe unused]]
[[maybe unused]] Attribute 2/2
• Functions
• Variables
• Classes and structures
• Enumerators
• Single value enumerator in C++17
• Types
• Namespaces
61/65
[[deprecated]] Attribute 2/4
struct S2 {
[[deprecated]] int var = 3;
[[deprecated]] static constexpr int var2 = 4;
};
63/65
[[deprecated]] Attribute and Namespace 4/4
void f() {}
} // namespace my_namespace
64/65
[[noreturn]] Attribute
[[noreturn]] indicates that a function does not return (e.g. program termination)
and the compiler should issue a compiler warning if the code contains other statements
that cannot be executed because it means a wrong user intention
[[noreturn]] void g() { std::exit(0); }
y = x + 1;
65/65