Referencing the Standard and Understanding the Standardization Process
Referencing
Standardization Process
Referencing
Standardization Process
C++ Stories
[Tip] How to Reference the C++ Standard or a Proposal
Youโre writing a document about C++, one feature or some cool programming technique. At one point you think that you have to prove that something works and thatโs why you need to quote text from the Standard. How to do it?
Intro Referencing the C++ Standardโฆ
Intro Referencing the C++ Standardโฆ
๐3
๐2
C/C++ Open Source Libraries
A list of open source C++ libraries
A list of open source C libraries
Another list for C++ libraries (May overlap with the previous one)
How to Build X from scratch - A collection of tutorials on how you can build from scratch something like an emulator, game engine, a container like Docker etc etc
A list of open source C++ libraries
A list of open source C libraries
Another list for C++ libraries (May overlap with the previous one)
How to Build X from scratch - A collection of tutorials on how you can build from scratch something like an emulator, game engine, a container like Docker etc etc
๐1
Member generation.png
273.1 KB
When are the special member functions (default constructor, copy/move constructor, copy/move assignment operators and destructor) generated?
The rules for generation of these special member functions is tabularized in the attached image.
The rules for generation of these special member functions is tabularized in the attached image.
๐1
How to compare floating point numbers in C++?
Comparison of floating point numbers is a tricky business as floating point numbers are not represented exactly by your computer. An approximation is what is stored. So you can't compare floating point numbers directly using == operator. This would result in the wrong answer.
To understand how floating point numbers are stored and why comparing floating point numbers using == would fail, read this article.
Assuming you have read the article above, this is how you would compare floating point numbers for equality in C++.
N here represents the approximate number of rounding errors you expect before you compare the floating point numbers. This can be 1 for most use cases.
The code above can be translated to C by defining epsilon to be a very small floating point number (how small depends on how accurate or the precision you desire for comparisons). The rest of the code is straightforward to implement.
Comparison of floating point numbers is a tricky business as floating point numbers are not represented exactly by your computer. An approximation is what is stored. So you can't compare floating point numbers directly using == operator. This would result in the wrong answer.
To understand how floating point numbers are stored and why comparing floating point numbers using == would fail, read this article.
Assuming you have read the article above, this is how you would compare floating point numbers for equality in C++.
template<typename T>
bool fequal(T x, T y, int N)
{
T diff = std::abs(x-y);
T tolerance = N*std::numeric_limits::epsilon();
return (diff <= tolerance*std::abs(x) && diff <= tolerance*std::abs(y));
}
N here represents the approximate number of rounding errors you expect before you compare the floating point numbers. This can be 1 for most use cases.
The code above can be translated to C by defining epsilon to be a very small floating point number (how small depends on how accurate or the precision you desire for comparisons). The rest of the code is straightforward to implement.
CodeProject
Succinct Guide to Floating Point Format For C++ and C# Programmers
๐3
Difference between keywords struct and class
Members of a class defined with the keyword class are private by default. Members of a class defined with the keyword struct are public by default.
In the absence of an access-specifier for a base class, public is assumed when the class is declared with the keyword struct and private is assumed when the class is declared using keyword class.
The keyword class can be used to declare template parameters while the keyword struct cannot be used to do this.
Advice : In general it is preferable to explicitly specify the access specifiers for the members of your class/struct instead of relying on the default.
Likewise it is preferred to make your base classes explicitly public, protected or private rather than relying on the default behavior depending on whether your class is declared using keyword struct or class.
This specifies your intentions clearly.
Now it brings us to the question of situations when a particular keyword should be preferred over the other.
You should use struct when you want to declare classes that are meant to behave like values i.e. classes which have very few methods and has public data. You should use the keyword class otherwise.
Members of a class defined with the keyword class are private by default. Members of a class defined with the keyword struct are public by default.
In the absence of an access-specifier for a base class, public is assumed when the class is declared with the keyword struct and private is assumed when the class is declared using keyword class.
The keyword class can be used to declare template parameters while the keyword struct cannot be used to do this.
Advice : In general it is preferable to explicitly specify the access specifiers for the members of your class/struct instead of relying on the default.
Likewise it is preferred to make your base classes explicitly public, protected or private rather than relying on the default behavior depending on whether your class is declared using keyword struct or class.
This specifies your intentions clearly.
Now it brings us to the question of situations when a particular keyword should be preferred over the other.
You should use struct when you want to declare classes that are meant to behave like values i.e. classes which have very few methods and has public data. You should use the keyword class otherwise.
๐2
What is Undefined Behavior?
(https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior)
Undefined behavior is one of those aspects of the C and C++ language that can be surprising to programmers coming from other languages (other languages try to hide it better). Basically, it is possible to write C++ programs that do not behave in a predictable way, even though many C++ compilers will not report any errors in the program!
Let's look at a classic example:
The variable
According to section 2.14.5 paragraph 11 of the C++ standard, it invokes undefined behavior:
The effect of attempting to modify a string literal is undefined.
I can hear people screaming "But wait, I can compile this no problem and get the output yellow" or "What do you mean undefined, string literals are stored in read-only memory, so the first assignment attempt results in a core dump". This is exactly the problem with undefined behavior. Basically, the standard allows anything to happen once you invoke undefined behavior (even nasal demons). If there is a "correct" behavior according to your mental model of the language, that model is simply wrong; The C++ standard has the only vote, period.
Other examples of undefined behavior include accessing an array beyond its bounds, dereferencing the null pointer, accessing objects after their lifetime ended or writing allegedly clever expressions like i++ + ++i.
Section 1.9 of the C++ standard also mentions undefined behavior's two less dangerous brothers, unspecified behavior and implementation-defined behavior:
Specifically, section 1.3.24 states:
--(continued)
(https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior)
Undefined behavior is one of those aspects of the C and C++ language that can be surprising to programmers coming from other languages (other languages try to hide it better). Basically, it is possible to write C++ programs that do not behave in a predictable way, even though many C++ compilers will not report any errors in the program!
Let's look at a classic example:
#include <iostream>
int main() {
char* p = "hello!\n"; // yes I know, deprecated conversion
p[0] = 'y';
p[5] = 'w';
std::cout << p;
}
The variable
p points to the string literal "hello!\n", and the two assignments below try to modify that string literal. What does this program do?According to section 2.14.5 paragraph 11 of the C++ standard, it invokes undefined behavior:
The effect of attempting to modify a string literal is undefined.
I can hear people screaming "But wait, I can compile this no problem and get the output yellow" or "What do you mean undefined, string literals are stored in read-only memory, so the first assignment attempt results in a core dump". This is exactly the problem with undefined behavior. Basically, the standard allows anything to happen once you invoke undefined behavior (even nasal demons). If there is a "correct" behavior according to your mental model of the language, that model is simply wrong; The C++ standard has the only vote, period.
Other examples of undefined behavior include accessing an array beyond its bounds, dereferencing the null pointer, accessing objects after their lifetime ended or writing allegedly clever expressions like i++ + ++i.
Section 1.9 of the C++ standard also mentions undefined behavior's two less dangerous brothers, unspecified behavior and implementation-defined behavior:
The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine.
Certain aspects and operations of the abstract machine are described in
this International Standard as implementation-defined (for example, sizeof(int)). These constitute the parameters of the abstract machine. Each implementation shall include documentation describing its characteristics and behavior in these respects.
Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function). Where possible, this International Standard defines a set of allowable behaviors. These define the nondeterministic aspects of the abstract machine.
Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer). [ Note: this International Standard imposes no requirements on the behavior of programs that contain undefined behavior. โend note ]
Specifically, section 1.3.24 states:
Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
--(continued)
Stack Overflow
Undefined, unspecified and implementation-defined behavior
What is undefined behavior (UB) in C and C++? What about unspecified behavior and implementation-defined behavior? What is the difference between them?
๐4
(continued) To understand the difference between Unspecified behavior and implementation defined behavior, you just have to know that the compiler has at times various (limited) ways it can execute your code. During the times where it can choose between any of these ways (because the standard imposes no specific choice and doesn't require the compiler to do so either), then the behavior is Unspecified.
During those times where the standard says that the compiler implementation can choose any of the ways but must also document which way it chooses, then the behavior is Implementation Defined.
For ex:
Here the compiler may decide to evaluate
If on the other hand, the standard required the compiler to document which argument it evaluated first, then Compiler A can choose to evaluate the first argument
According to the C++ standard, the order of evaluation of arguments is Unspecified Behavior.
During those times where the standard says that the compiler implementation can choose any of the ways but must also document which way it chooses, then the behavior is Implementation Defined.
For ex:
int a = f(x) + g(y);Here the compiler may decide to evaluate
g(y) before f(x) or vice-versa. The standard imposes no requirements on which argument to operator+ is evaluated first and it also doesn't require the compiler to document which operand it evaluated first. So this is an example of Unspecified Behavior.If on the other hand, the standard required the compiler to document which argument it evaluated first, then Compiler A can choose to evaluate the first argument
f(x) first always and compiler B can choose to evaluate g(y), the second operand first always. Both of these are acceptable according to the standard. This would be an example of Implementation Defined Behavior.According to the C++ standard, the order of evaluation of arguments is Unspecified Behavior.
๐4
What is the difference between public, protected and private inheritance in C++?
Let's consider a class Base and a class Child that inherits from Base.
If the inheritance is public, everything that is aware of Base and Child is also aware that Child inherits from Base.
If the inheritance is protected, only Child, and its children, are aware that they inherit from Base.
If the inheritance is private, no one other than Child is aware of the inheritance.
Example:
class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B
};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A // 'private' is default for classes
{
// x is private
// y is private
// z is not accessible from D
};
Let's consider a class Base and a class Child that inherits from Base.
If the inheritance is public, everything that is aware of Base and Child is also aware that Child inherits from Base.
If the inheritance is protected, only Child, and its children, are aware that they inherit from Base.
If the inheritance is private, no one other than Child is aware of the inheritance.
Example:
class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B
};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A // 'private' is default for classes
{
// x is private
// y is private
// z is not accessible from D
};
๐3
I know what lvalues and rvalues are. But what do people mean when they say xvalues, glvalues and prvalues?
(From Stackoverflow)
An lvalue (so-called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. [Example: If E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the result of calling a function whose return type is an lvalue reference is an lvalue.]
An xvalue (an โeXpiringโ value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references. [Example: The result of calling a function whose return type is an rvalue reference is an xvalue.]
A glvalue (โgeneralizedโ lvalue) is an lvalue or an xvalue.
An rvalue (so-called, historically, because rvalues could appear on the right-hand side of an assignment expression) is an xvalue, a temporary object or subobject thereof, or a value that is not associated with an object.
A prvalue (โpureโ rvalue) is an rvalue that is not an xvalue. [Example: The result of calling a function whose return type is not a reference is a prvalue]
(From Stackoverflow)
An lvalue (so-called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. [Example: If E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the result of calling a function whose return type is an lvalue reference is an lvalue.]
An xvalue (an โeXpiringโ value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references. [Example: The result of calling a function whose return type is an rvalue reference is an xvalue.]
A glvalue (โgeneralizedโ lvalue) is an lvalue or an xvalue.
An rvalue (so-called, historically, because rvalues could appear on the right-hand side of an assignment expression) is an xvalue, a temporary object or subobject thereof, or a value that is not associated with an object.
A prvalue (โpureโ rvalue) is an rvalue that is not an xvalue. [Example: The result of calling a function whose return type is not a reference is a prvalue]
๐2
What is Copy Elision and Return Value Optimization?
(From Stackoverflow)
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
It's the only form of optimization that elides (ha!) the as-if rule - copy elision can be applied even if copying/moving the object has side-effects.
The following example taken from Wikipedia:
Depending on the compiler & settings, the following outputs are all valid:
This also means fewer objects can be created, so you also can't rely on a specific number of destructors being called. You shouldn't have critical logic inside copy/move-constructors or destructors, as you can't rely on them being called.
If a call to a copy or move constructor is elided, that constructor must still exist and must be accessible. This ensures that copy elision does not allow copying objects which are not normally copyable, e.g. because they have a private or deleted copy/move constructor.
C++17: As of C++17, Copy Elision is guaranteed when an object is returned directly:
(Named) Return value optimization is a common form of copy elision. It refers to the situation where an object returned by value from a method has its copy elided. The example set forth in the standard illustrates named return value optimization, since the object is named.
Regular return value optimization occurs when a temporary is returned:
(From Stackoverflow)
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
It's the only form of optimization that elides (ha!) the as-if rule - copy elision can be applied even if copying/moving the object has side-effects.
The following example taken from Wikipedia:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
Depending on the compiler & settings, the following outputs are all valid:
Hello World!
A copy was made.
A copy was made.
Hello World!
A copy was made.
Hello World!
This also means fewer objects can be created, so you also can't rely on a specific number of destructors being called. You shouldn't have critical logic inside copy/move-constructors or destructors, as you can't rely on them being called.
If a call to a copy or move constructor is elided, that constructor must still exist and must be accessible. This ensures that copy elision does not allow copying objects which are not normally copyable, e.g. because they have a private or deleted copy/move constructor.
C++17: As of C++17, Copy Elision is guaranteed when an object is returned directly:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}
int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}
(Named) Return value optimization is a common form of copy elision. It refers to the situation where an object returned by value from a method has its copy elided. The example set forth in the standard illustrates named return value optimization, since the object is named.
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
Regular return value optimization occurs when a temporary is returned:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();๐4
What is external linkage and internal linkage?
When you write an implementation file (.cpp, .cxx, etc) your compiler generates a translation unit. This is the source file from your implementation plus all the headers you #included in it.
Internal linkage refers to everything only in scope of a translation unit.
External linkage refers to things that exist beyond a particular translation unit. In other words, accessible through the whole program, which is the combination of all translation units (or object files).
You can explicitly control the linkage of a symbol by using the extern and static keywords. If the linkage is not specified then the default linkage is extern (external linkage) for non-const symbols and static (internal linkage) for const symbols.
Note that instead of using static (internal linkage), it is better to use anonymous namespaces into which you can also put classes. Though they allow extern linkage, anonymous namespaces are unreachable from other translation units, making linkage effectively static.
When you write an implementation file (.cpp, .cxx, etc) your compiler generates a translation unit. This is the source file from your implementation plus all the headers you #included in it.
Internal linkage refers to everything only in scope of a translation unit.
External linkage refers to things that exist beyond a particular translation unit. In other words, accessible through the whole program, which is the combination of all translation units (or object files).
You can explicitly control the linkage of a symbol by using the extern and static keywords. If the linkage is not specified then the default linkage is extern (external linkage) for non-const symbols and static (internal linkage) for const symbols.
// In namespace scope or global scope.
int i; // extern by default
const int ci; // static by default
inline const int ci; // external linkage since C++17
extern const int eci; // explicitly extern
static int si; // explicitly static
// The same goes for functions (but there are no const functions).
int f(); // extern by default
static int sf(); // explicitly static
Note that instead of using static (internal linkage), it is better to use anonymous namespaces into which you can also put classes. Though they allow extern linkage, anonymous namespaces are unreachable from other translation units, making linkage effectively static.
namespace {
int i; // extern by default but unreachable from other translation units
class C; // extern by default but unreachable from other translation units
}๐2
What is span and when should we use it?
(From Stackoverflow)
A
A very lightweight abstraction of a contiguous sequence of values of type
Basically a
A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.
It was formerly known as an
When should I use it?
First, when not to use it:
Don't use it in code that could just take any pair of start & end iterators, like
Don't use it if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. It's not intended to supplant any of them.
Now for when to actually use it:
Use
with:
Why should I use it? Why is it a good thing?
Oh, spans are awesome! Using a span...
- means that you can work with that pointer+length / start+end pointer combination like you would with a fancy, pimped-out standard library container, e.g.:
... but with absolutely none of the overhead most container classes incur.
- lets the compiler do more work for you sometimes. For example, this:
becomes this:
which will do what you would want it to do.
- is the reasonable alternative to passing const vector<T>& to functions when you expect your data to be contiguous in memory. No more getting scolded by high-and-mighty C++ gurus!
- facilitates static analysis, so the compiler might be able to help you catch silly bugs.
- allows for debug-compilation instrumentation for runtime bounds-checking (i.e. span's methods will have some bounds-checking code within
- indicates that your code (that's using the span) doesn't own the pointed-to memory.
There's even more motivation for using spans, which you could find in the C++ core guidelines - but you catch the drift.
(From Stackoverflow)
A
span<T> introduced in C++20 is:A very lightweight abstraction of a contiguous sequence of values of type
T somewhere in memory.Basically a
struct { T * ptr; std::size_t length; } with a bunch of convenience methods. It is similar to a slice or a fat pointer in Rust.A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.
It was formerly known as an
array_view and even earlier as array_ref.When should I use it?
First, when not to use it:
Don't use it in code that could just take any pair of start & end iterators, like
std::sort, std::find_if, std::copy and all of those super-generic templated functions.Don't use it if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. It's not intended to supplant any of them.
Now for when to actually use it:
Use
span<T> (respectively, span<const T>) instead of a free-standing T* (respectively const T*) when the allocated length or size also matter. So, replace functions like:void read_into(int* buffer, size_t buffer_size);with:
void read_into(span<int> buffer);Why should I use it? Why is it a good thing?
Oh, spans are awesome! Using a span...
- means that you can work with that pointer+length / start+end pointer combination like you would with a fancy, pimped-out standard library container, e.g.:
for (auto& x : my_span) { /* do stuff */ }
std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);
std::ranges::find_if(my_span, some_predicate);
... but with absolutely none of the overhead most container classes incur.
- lets the compiler do more work for you sometimes. For example, this:
int buffer[BUFFER_SIZE];
read_into(buffer, BUFFER_SIZE);
becomes this:
int buffer[BUFFER_SIZE]; read_into(buffer);
which will do what you would want it to do.
- is the reasonable alternative to passing const vector<T>& to functions when you expect your data to be contiguous in memory. No more getting scolded by high-and-mighty C++ gurus!
- facilitates static analysis, so the compiler might be able to help you catch silly bugs.
- allows for debug-compilation instrumentation for runtime bounds-checking (i.e. span's methods will have some bounds-checking code within
#ifndef NDEBUG ... #endif)- indicates that your code (that's using the span) doesn't own the pointed-to memory.
There's even more motivation for using spans, which you could find in the C++ core guidelines - but you catch the drift.
๐4
What are the different stages of compilation?
The compilation of a C++ program involves three steps:
Preprocessing: the preprocessor takes a C++ source code file and deals with the
Compilation: the compiler takes the pre-processor's output and produces an object file from it.
Linking: the linker takes the object files produced by the compiler and produces either a library or an executable file.
Preprocessing
The preprocessor handles the preprocessor directives, like
It works on one C++ source file at a time by replacing
The preprocessor works on a stream of preprocessing tokens. Macro substitution is defined as replacing tokens with other tokens (the operator ## enables merging two tokens when it makes sense).
After all this, the preprocessor produces a single output that is a stream of tokens resulting from the transformations described above. It also adds some special markers that tell the compiler where each line came from so that it can use those to produce sensible error messages.
Some errors can be produced at this stage with clever use of the
Compilation
The compilation step is performed on each output of the preprocessor. The compiler parses the pure C++ source code (now without any preprocessor directives) and converts it into assembly code. Then invokes underlying back-end(assembler in toolchain) that assembles that code into machine code producing actual binary file in some format(ELF, COFF, a.out, ...). This object file contains the compiled code (in binary form) of the symbols defined in the input. Symbols in object files are referred to by name.
Object files can refer to symbols that are not defined. This is the case when you use a declaration, and don't provide a definition for it. The compiler doesn't mind this, and will happily produce the object file as long as the source code is well-formed.
Compilers usually let you stop compilation at this point. This is very useful because with it you can compile each source code file separately. The advantage this provides is that you don't need to recompile everything if you only change a single file.
The produced object files can be put in special archives called static libraries, for easier reusing later on.
It's at this stage that "regular" compiler errors, like syntax errors or failed overload resolution errors, are reported.
Linking
The linker is what produces the final compilation output from the object files the compiler produced. This output can be either a shared (or dynamic) library (and while the name is similar, they haven't got much in common with static libraries mentioned earlier) or an executable.
It links all the object files by replacing the references to undefined symbols with the correct addresses. Each of these symbols can be defined in other object files or in libraries. If they are defined in libraries other than the standard library, you need to tell the linker about them.
At this stage the most common errors are missing definitions or duplicate definitions. The former means that either the definitions don't exist (i.e. they are not written), or that the object files or libraries where they reside were not given to the linker. The latter is obvious: the same symbol was defined in two different object files or libraries.
The compilation of a C++ program involves three steps:
Preprocessing: the preprocessor takes a C++ source code file and deals with the
#includes, #defines and other preprocessor directives. The output of this step is a "pure" C++ file without pre-processor directives.Compilation: the compiler takes the pre-processor's output and produces an object file from it.
Linking: the linker takes the object files produced by the compiler and produces either a library or an executable file.
Preprocessing
The preprocessor handles the preprocessor directives, like
#include and #define. It is agnostic of the syntax of C++, which is why it must be used with care.It works on one C++ source file at a time by replacing
#include directives with the content of the respective files (which is usually just declarations), doing replacement of macros (#define), and selecting different portions of text depending of #if, #ifdef and #ifndef directives.The preprocessor works on a stream of preprocessing tokens. Macro substitution is defined as replacing tokens with other tokens (the operator ## enables merging two tokens when it makes sense).
After all this, the preprocessor produces a single output that is a stream of tokens resulting from the transformations described above. It also adds some special markers that tell the compiler where each line came from so that it can use those to produce sensible error messages.
Some errors can be produced at this stage with clever use of the
#if and #error directives.Compilation
The compilation step is performed on each output of the preprocessor. The compiler parses the pure C++ source code (now without any preprocessor directives) and converts it into assembly code. Then invokes underlying back-end(assembler in toolchain) that assembles that code into machine code producing actual binary file in some format(ELF, COFF, a.out, ...). This object file contains the compiled code (in binary form) of the symbols defined in the input. Symbols in object files are referred to by name.
Object files can refer to symbols that are not defined. This is the case when you use a declaration, and don't provide a definition for it. The compiler doesn't mind this, and will happily produce the object file as long as the source code is well-formed.
Compilers usually let you stop compilation at this point. This is very useful because with it you can compile each source code file separately. The advantage this provides is that you don't need to recompile everything if you only change a single file.
The produced object files can be put in special archives called static libraries, for easier reusing later on.
It's at this stage that "regular" compiler errors, like syntax errors or failed overload resolution errors, are reported.
Linking
The linker is what produces the final compilation output from the object files the compiler produced. This output can be either a shared (or dynamic) library (and while the name is similar, they haven't got much in common with static libraries mentioned earlier) or an executable.
It links all the object files by replacing the references to undefined symbols with the correct addresses. Each of these symbols can be defined in other object files or in libraries. If they are defined in libraries other than the standard library, you need to tell the linker about them.
At this stage the most common errors are missing definitions or duplicate definitions. The former means that either the definitions don't exist (i.e. they are not written), or that the object files or libraries where they reside were not given to the linker. The latter is obvious: the same symbol was defined in two different object files or libraries.
๐5
What are circular dependencies and how to break them?
Imagine you are writing a compiler. And you see code like this.
When you are compiling the .cc file (remember that the .cc and not the .h is the unit of compilation as mentioned here), you need to allocate space for object A. So, well, how much space then? Enough to store B! What's the size of B then? Enough to store A! Oops.
Clearly a circular reference that you must break.
You can break it by allowing the compiler to instead reserve as much space as it knows about upfront - pointers and references, for example, will always be 32 or 64 bits (depending on the architecture) and so if you replaced (either one) by a pointer or reference, things would be great. Let's say we replace in A:
Now things are better. Somewhat. main() still says:
#include, for all extents and purposes (if you take the preprocessor out) just copies the file into the .cc. So really, the .cc looks like:
You can see why the compiler can't deal with this - it has no idea what B is - it has never even seen the symbol before.
So let's tell the compiler about B. This is known as a forward declaration, and is discussed further in this answer.
This works. It is not great. But at this point you should have an understanding of the circular reference problem and what we did to "fix" it, albeit the fix is bad.
The reason this fix is bad is because the next person to #include "A.h" will have to declare B before they can use it and will get a terrible #include error. So let's move the declaration into A.h itself.
And in B.h, at this point, you can just #include "A.h" directly.
Imagine you are writing a compiler. And you see code like this.
// file: A.h
class A {
B _b;
};
// file: B.h
class B {
A _a;
};
// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
A a;
}When you are compiling the .cc file (remember that the .cc and not the .h is the unit of compilation as mentioned here), you need to allocate space for object A. So, well, how much space then? Enough to store B! What's the size of B then? Enough to store A! Oops.
Clearly a circular reference that you must break.
You can break it by allowing the compiler to instead reserve as much space as it knows about upfront - pointers and references, for example, will always be 32 or 64 bits (depending on the architecture) and so if you replaced (either one) by a pointer or reference, things would be great. Let's say we replace in A:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};Now things are better. Somewhat. main() still says:
// file: main.cc
#include "A.h" //Problem here#include, for all extents and purposes (if you take the preprocessor out) just copies the file into the .cc. So really, the .cc looks like:
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}You can see why the compiler can't deal with this - it has no idea what B is - it has never even seen the symbol before.
So let's tell the compiler about B. This is known as a forward declaration, and is discussed further in this answer.
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}This works. It is not great. But at this point you should have an understanding of the circular reference problem and what we did to "fix" it, albeit the fix is bad.
The reason this fix is bad is because the next person to #include "A.h" will have to declare B before they can use it and will get a terrible #include error. So let's move the declaration into A.h itself.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};And in B.h, at this point, you can just #include "A.h" directly.
// file: B.h
#include "A.h"
class B {
// note that this is cool because the compiler knows by this time
// how much space A will need.
A _a;
}Telegram
C/C++ Resources and FAQ
What are the different stages of compilation?
The compilation of a C++ program involves three steps:
Preprocessing: the preprocessor takes a C++ source code file and deals with the #includes, #defines and other preprocessor directives. The output of thisโฆ
The compilation of a C++ program involves three steps:
Preprocessing: the preprocessor takes a C++ source code file and deals with the #includes, #defines and other preprocessor directives. The output of thisโฆ
๐8
What is std::expected and how can it be used?
What it is and when it's used
Here are three complementing explanations of what an
It is the return type of a function which is supposed to return a
An example:
It's an error-handling mechanism, being an alternative to throwing exceptions (in which case you always return the value you were supposed to), and to returning status/error codes (in which case you never return the value you want to, and have to use an out-parameter).
Here are the two alternative error-handling mechanisms applied to the function from the previous example:
It's discriminated union of types
How is it better than just a
It behaves somewhat like
Actually, that last mode of access behaves differently than
"Hey, I looked for it in the standard and it isn't there!"
This being said - it is quite usable already, since it requires no new language facilities. I would recommend Sy Brand (tartanllama)'s implementation, which can be used with C++11 or later. It also has some neat functional-style extensions (which may not be standardized).
What it is and when it's used
Here are three complementing explanations of what an
std::expected<T, E> is:It is the return type of a function which is supposed to return a
T value - but which may encounter some error, in which case it will return a descriptor of that error, of type E.An example:
std::expected<ParsedData, ParsingError> parse_input(Input input);It's an error-handling mechanism, being an alternative to throwing exceptions (in which case you always return the value you were supposed to), and to returning status/error codes (in which case you never return the value you want to, and have to use an out-parameter).
Here are the two alternative error-handling mechanisms applied to the function from the previous example:
ParsedData parse_input_2(Input input) noexcept(false);
ParsingError parse_input_3(ParsedData& result, Input input);
It's discriminated union of types
T and E with some convenience methods.How is it better than just a
std::variant<T,E>?It behaves somewhat like
std::optional<T>, giving focus to the expected, rather than the unexpected, case:result.has_value() - true if we got a value rather than an error.if (result) - checks for the same thing*result - gives us the T value if it exists, undefined behavior otherwise (same as std::optional, although many don't like this).result.value(), gives us the T value if it exists, or throws otherwise.Actually, that last mode of access behaves differently than
std::optional if we got an error: What it throws is a bad_expected_access<E>, with the returned error. This behavior can be thought of as a way to switch from expected-based to exception-based error-handling."Hey, I looked for it in the standard and it isn't there!"
std::expected will be part of the upcoming C++23 standard.This being said - it is quite usable already, since it requires no new language facilities. I would recommend Sy Brand (tartanllama)'s implementation, which can be used with C++11 or later. It also has some neat functional-style extensions (which may not be standardized).
๐4
What is std::launder and what is it used for?
std::launder is aptly named, though only if you know what it's for. It performs memory laundering.
Consider the example in the paper:
That statement performs aggregate initialization, initializing the first member of
Because
So what happens if we do this:
Because X is trivial, we need not destroy the old object before creating a new one in its place, so this is perfectly legal code. The new object will have its n member be 2.
So tell me, what will
The obvious answer will be 2. But that's wrong, because the compiler is allowed to assume that a truly const variable (not merely a
[basic.life]/8 spells out the circumstances when it is OK to access the newly created object through variables/pointers/references to the old one. And having a const member is one of the disqualifying factors.
So... how can we talk about
We have to launder our memory:
Money laundering is used to prevent people from tracing where you got your money from. Memory laundering is used to prevent the compiler from tracing where you got your object from, thus forcing it to avoid any optimizations that may no longer apply.
Another of the disqualifying factors is if you change the type of the object. std::launder can help here too:
[basic.life]/8 tells us that, if you allocate a new object in the storage of the old one, you cannot access the new object through pointers to the old. launder allows us to side-step that.
std::launder is aptly named, though only if you know what it's for. It performs memory laundering.
Consider the example in the paper:
struct X { const int n; };
union U { X x; float f; }; ...
U u = {{ 1 }};
That statement performs aggregate initialization, initializing the first member of
U with {1}.Because
n is a const variable, the compiler is free to assume that u.x.n shall always be 1.So what happens if we do this:
X *p = new (&u.x) X {2}; Because X is trivial, we need not destroy the old object before creating a new one in its place, so this is perfectly legal code. The new object will have its n member be 2.
So tell me, what will
u.x.n return?The obvious answer will be 2. But that's wrong, because the compiler is allowed to assume that a truly const variable (not merely a
const&, but an object variable declared const) will never change. But we just changed it.[basic.life]/8 spells out the circumstances when it is OK to access the newly created object through variables/pointers/references to the old one. And having a const member is one of the disqualifying factors.
So... how can we talk about
u.x.n properly?We have to launder our memory:
assert(*std::launder(&u.x.n) == 2); //Will be true. Money laundering is used to prevent people from tracing where you got your money from. Memory laundering is used to prevent the compiler from tracing where you got your object from, thus forcing it to avoid any optimizations that may no longer apply.
Another of the disqualifying factors is if you change the type of the object. std::launder can help here too:
alignas(int) char data[sizeof(int)];
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));
[basic.life]/8 tells us that, if you allocate a new object in the storage of the old one, you cannot access the new object through pointers to the old. launder allows us to side-step that.
๐4
Capture of this pointer in lambdas has changed constantly since C++11. The attached image captures what has changed in each version of the standard.
While reading the image, remember the following two things:
. [this] is semantically equivalent to [&(*this)] i.e. when the lambda capture includes this, it means a by reference capture of the object pointed to by this pointer. No copies of the current object are made.
. [*this] is equivalent to a by value capture of the object pointed to by this pointer. A copy of the current object is made.
While reading the image, remember the following two things:
. [this] is semantically equivalent to [&(*this)] i.e. when the lambda capture includes this, it means a by reference capture of the object pointed to by this pointer. No copies of the current object are made.
. [*this] is equivalent to a by value capture of the object pointed to by this pointer. A copy of the current object is made.
๐3
What is "Deducing this" feature slated for C++23 and how does it help?
https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
In the blog above, Sy Brand (one of the authors of std::optional) explains what this feature brings to the table. They have summarized all of "Deducing this"s use cases in one of the best written blogs in recent past.
https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
In the blog above, Sy Brand (one of the authors of std::optional) explains what this feature brings to the table. They have summarized all of "Deducing this"s use cases in one of the best written blogs in recent past.
Microsoft News
C++23โs Deducing this: what it is, why it is, how to use it
Find out how C++23's Deducing this feature can help make your code better.
๐2