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
What are Template Deduction Guides (introduced in C++17) and when and how to use them?
Template deduction guides are patterns associated with a template class that tell the compiler how to translate a set of constructor arguments (and their types) into template parameters for the class.
The simplest example is that of std::vector and its constructor that takes an iterator pair.
The compiler needs to figure out what
You use a deduction guide:
This tells the compiler that, when you call a vector constructor matching that pattern, it will deduce the vector specialization using the code on the right of ->.
You need guides when the deduction of the type from the arguments is not based on the type of one of those arguments. Initializing a vector from an
The left side doesn't necessarily specify an actual constructor. The way it works is that, if you use template constructor deduction on a type, it matches the arguments you pass against all deduction guides (actual constructors of the primary template provide implicit guides). If there is a match, it uses that to determine which template arguments to provide to the type.
But once that deduction is done, once the compiler figures out the template parameters for the type, initialization for the object of that type proceeds as if none of that happened. That is, the deduction guide selected does not have to match the constructor selected.
This also means that you can use guides with aggregates and aggregate initialization:
So deduction guides are only used to figure out the type being initialized. The actual process of initialization works exactly as it did before, once that determination has been made.
Template deduction guides are patterns associated with a template class that tell the compiler how to translate a set of constructor arguments (and their types) into template parameters for the class.
The simplest example is that of std::vector and its constructor that takes an iterator pair.
template<typename Iterator>
void func(Iterator first, Iterator last)
{
vector v(first, last);
}
The compiler needs to figure out what
vector<T>s T type will be. We know what the answer is; T should be typename std::iterator_traits<Iterator>::value_type. But how do we tell the compiler without having to type vector<typename std::iterator_traits<Iterator>::value_type>?You use a deduction guide:
template<typename Iterator> vector(Iterator b, Iterator e) ->
vector<typename std::iterator_traits<Iterator>::value_type>;
This tells the compiler that, when you call a vector constructor matching that pattern, it will deduce the vector specialization using the code on the right of ->.
You need guides when the deduction of the type from the arguments is not based on the type of one of those arguments. Initializing a vector from an
initializer_list explicitly uses the vector's T, so it doesn't need a guide.The left side doesn't necessarily specify an actual constructor. The way it works is that, if you use template constructor deduction on a type, it matches the arguments you pass against all deduction guides (actual constructors of the primary template provide implicit guides). If there is a match, it uses that to determine which template arguments to provide to the type.
But once that deduction is done, once the compiler figures out the template parameters for the type, initialization for the object of that type proceeds as if none of that happened. That is, the deduction guide selected does not have to match the constructor selected.
This also means that you can use guides with aggregates and aggregate initialization:
template<typename T>
struct Thingy
{
T t;
};
Thingy(const char *) -> Thingy<std::string>;
Thingy thing{"A String"}; //thing.t is a `std::string`.
So deduction guides are only used to figure out the type being initialized. The actual process of initialization works exactly as it did before, once that determination has been made.
π4
Linker errors are so hard to understand because of the mangled names. Is there a way to see the actual function names instead of these weird mangled names?
When you compile and link your code, you can pass an additional option
to the linker specifying whether any error output from the linker should
show demangled names or mangled names.
Consider the following code:
we will get the following linker error:
If we don't want to see function names like
The linker error would now look like this:
The -Wl option is used to pass flags to the linker. The flag passed to the linker in this case is --demangle which indicates to the linker that any output from it should demangle the
names.
When you compile and link your code, you can pass an additional option
to the linker specifying whether any error output from the linker should
show demangled names or mangled names.
Consider the following code:
void foo();
void foo(int);
int main() {
foo();
foo(5);
}
If we compile it like this:c++ main.cpp -o mainwe will get the following linker error:
main.o: In function`main':
main.cpp:(.text+0x5): undefined reference to `_Z3foov'
main.cpp:(.text+0xf): undefined reference to `_Z3fooi'
collect2: error: ld returned 1 exit status
If we don't want to see function names like
_Z3foov and _Z3fooi, we can pass an additional flag to the linker asking it to demangle the names in its error output like this:c++ main.cpp -Wl,--demangle -o mainThe linker error would now look like this:
main.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `foo()'
main.cpp:(.text+0xf): undefined reference to `foo(int)'
collect2: error: ld returned 1 exit status
The -Wl option is used to pass flags to the linker. The flag passed to the linker in this case is --demangle which indicates to the linker that any output from it should demangle the
names.
π11
What is Standard Memory Model that was introduced in C++11 and how does it help?
The C++ specification does not make reference to any particular compiler, operating system, or CPU. It makes reference to an abstract machine that is a generalization of actual systems. In the Language Lawyer world, the job of the programmer is to write code for the abstract machine; the job of the compiler is to actualize that code on a concrete machine. By coding rigidly to the spec, you can be certain that your code will compile and run without modification on any system with a compliant C++ compiler, whether today or 50 years from now.
The abstract machine in the C++98/C++03 specification is fundamentally single-threaded. So it is not possible to write multi-threaded C++ code that is "fully portable" with respect to the spec. The spec does not even say anything about the atomicity of memory loads and stores or the order in which loads and stores might happen, never mind things like mutexes.
Of course, you can write multi-threaded code in practice for particular concrete systems β like pthreads or Windows. But there is no standard way to write multi-threaded code for C++98/C++03.
The abstract machine in C++11 is multi-threaded by design. It also has a well-defined memory model; that is, it says what the compiler may and may not do when it comes to accessing memory.
Consider the following example, where a pair of global variables are accessed concurrently by two threads:
What might Thread 2 output?
Under C++98/C++03, this is not even Undefined Behavior; the question itself is meaningless because the standard does not contemplate anything called a "thread".
Under C++11, the result is Undefined Behavior, because loads and stores need not be atomic in general. Which may not seem like much of an improvement. And by itself, it's not.
But with C++11, you can write this:
Now things get much more interesting. First of all, the behavior here is defined. Thread 2 could now print 0 0 (if it runs before Thread 1), 37 17 (if it runs after Thread 1), or 0 17 (if it runs after Thread 1 assigns to x but before it assigns to y).
What it cannot print is 37 0, because the default mode for atomic loads/stores in C++11 is to enforce sequential consistency. This just means all loads and stores must be "as if" they happened in the order you wrote them within each thread, while operations among threads can be interleaved however the system likes. So the default behavior of atomics provides both atomicity and ordering for loads and stores.
Now, on a modern CPU, ensuring sequential consistency can be expensive. In particular, the compiler is likely to emit full-blown memory barriers between every access here. But if your algorithm can tolerate out-of-order loads and stores; i.e., if it requires atomicity but not ordering; i.e., if it can tolerate 37 0 as output from this program, then you can write this:
The more modern the CPU, the more likely this is to be faster than the previous example.
Finally, if you just need to keep particular loads and stores in order, you can write:
This takes us back to the ordered loads and stores β so 37 0 is no longer a possible output β but it does so with minimal overhead. (In this trivial example, the result is the same as full-blown sequential
The C++ specification does not make reference to any particular compiler, operating system, or CPU. It makes reference to an abstract machine that is a generalization of actual systems. In the Language Lawyer world, the job of the programmer is to write code for the abstract machine; the job of the compiler is to actualize that code on a concrete machine. By coding rigidly to the spec, you can be certain that your code will compile and run without modification on any system with a compliant C++ compiler, whether today or 50 years from now.
The abstract machine in the C++98/C++03 specification is fundamentally single-threaded. So it is not possible to write multi-threaded C++ code that is "fully portable" with respect to the spec. The spec does not even say anything about the atomicity of memory loads and stores or the order in which loads and stores might happen, never mind things like mutexes.
Of course, you can write multi-threaded code in practice for particular concrete systems β like pthreads or Windows. But there is no standard way to write multi-threaded code for C++98/C++03.
The abstract machine in C++11 is multi-threaded by design. It also has a well-defined memory model; that is, it says what the compiler may and may not do when it comes to accessing memory.
Consider the following example, where a pair of global variables are accessed concurrently by two threads:
//Global
int x, y;
//Thread 1
x = 17;
y = 37;
//Thread 2
cout << y << " ";
cout << x << endl;
What might Thread 2 output?
Under C++98/C++03, this is not even Undefined Behavior; the question itself is meaningless because the standard does not contemplate anything called a "thread".
Under C++11, the result is Undefined Behavior, because loads and stores need not be atomic in general. Which may not seem like much of an improvement. And by itself, it's not.
But with C++11, you can write this:
\\Global
atomic<int> x, y;
\\Thread 1
x.store(17);
y.store(37);
\\Thread 2
cout << y.load() << " ";
cout << x.load() << endl;
Now things get much more interesting. First of all, the behavior here is defined. Thread 2 could now print 0 0 (if it runs before Thread 1), 37 17 (if it runs after Thread 1), or 0 17 (if it runs after Thread 1 assigns to x but before it assigns to y).
What it cannot print is 37 0, because the default mode for atomic loads/stores in C++11 is to enforce sequential consistency. This just means all loads and stores must be "as if" they happened in the order you wrote them within each thread, while operations among threads can be interleaved however the system likes. So the default behavior of atomics provides both atomicity and ordering for loads and stores.
Now, on a modern CPU, ensuring sequential consistency can be expensive. In particular, the compiler is likely to emit full-blown memory barriers between every access here. But if your algorithm can tolerate out-of-order loads and stores; i.e., if it requires atomicity but not ordering; i.e., if it can tolerate 37 0 as output from this program, then you can write this:
\\Global
atomic<int> x, y;
\\Thread 1
x.store(17,memory_order_relaxed);
y.store(37,memory_order_relaxed);
\\Thread 2
cout << y.load(memory_order_relaxed) << " ";
cout << x.load(memory_order_relaxed) << endl;
The more modern the CPU, the more likely this is to be faster than the previous example.
Finally, if you just need to keep particular loads and stores in order, you can write:
\\Global
atomic<int> x, y;
\\Thread 1
x.store(17,memory_order_release);
y.store(37,memory_order_release);
\\Thread 2
cout << y.load(memory_order_acquire) << " ";
cout << x.load(memory_order_acquire) << endl;
This takes us back to the ordered loads and stores β so 37 0 is no longer a possible output β but it does so with minimal overhead. (In this trivial example, the result is the same as full-blown sequential
π7
in a larger program, it would not be.)
Of course, if the only outputs you want to see are 0 0 or 37 17, you can just wrap a mutex around the original code. But if you have read this far, I bet you already know how that works, and this answer is already longer than I intended :-).
So, bottom line. Mutexes are great, and C++11 standardizes them. But sometimes for performance reasons you want lower-level primitives (e.g., the classic double-checked locking pattern). The new standard provides high-level gadgets like mutexes and condition variables, and it also provides low-level gadgets like atomic types and the various flavors of memory barrier. So now you can write sophisticated, high-performance concurrent routines entirely within the language specified by the standard, and you can be certain your code will compile and run unchanged on both today's systems and tomorrow's.
Although to be frank, unless you are an expert and working on some serious low-level code, you should probably stick to mutexes and condition variables.
For more on this stuff, see this blog post.
Of course, if the only outputs you want to see are 0 0 or 37 17, you can just wrap a mutex around the original code. But if you have read this far, I bet you already know how that works, and this answer is already longer than I intended :-).
So, bottom line. Mutexes are great, and C++11 standardizes them. But sometimes for performance reasons you want lower-level primitives (e.g., the classic double-checked locking pattern). The new standard provides high-level gadgets like mutexes and condition variables, and it also provides low-level gadgets like atomic types and the various flavors of memory barrier. So now you can write sophisticated, high-performance concurrent routines entirely within the language specified by the standard, and you can be certain your code will compile and run unchanged on both today's systems and tomorrow's.
Although to be frank, unless you are an expert and working on some serious low-level code, you should probably stick to mutexes and condition variables.
For more on this stuff, see this blog post.
π4
A collection of lock free and concurrent data structures.
https://github.com/mpoeter/xenium
I have used some of them as a reference implementation and I can vouch for the code quality.
https://github.com/mpoeter/xenium
I have used some of them as a reference implementation and I can vouch for the code quality.
GitHub
GitHub - mpoeter/xenium: A C++ library providing various concurrent data structures and reclamation schemes.
A C++ library providing various concurrent data structures and reclamation schemes. - mpoeter/xenium
π5
Collection of various algorithms in mathematics, machine learning, computer science, physics, etc implemented in C++ for educational purposes.
https://github.com/TheAlgorithms/C-Plus-Plus
https://github.com/TheAlgorithms/C-Plus-Plus
GitHub
GitHub - TheAlgorithms/C-Plus-Plus: Collection of various algorithms in mathematics, machine learning, computer science and physicsβ¦
Collection of various algorithms in mathematics, machine learning, computer science and physics implemented in C++ for educational purposes. - TheAlgorithms/C-Plus-Plus
π5
How can I use RAII to manage resources programmed using a C-style API?
There is an easy way to use RAII to manage resources from a C-style interface: the standard library's smart pointers, which come in two flavors:
If you want simple, scope-based resource management,
You can do much the same with
Now, both of these are all well and good, but the standard library has
GotW#56 mentions that the evaluation of arguments to a function are unordered, which means if you have a function that takes your shiny new
means that the instructions might be ordered like this:
Now our precious C interface resource won't be cleaned up properly if step 2 throws because the
Again as mentioned in GotW #56, there's actually a relatively simple way to deal with the exception safety problem. Unlike expression evaluations in function arguments, function evaluations can't be interleaved. So if we acquire a resource and give it to a
You can use it like this:
and call func worry-free, like this:
The compiler can't take the construction of
There is an easy way to use RAII to manage resources from a C-style interface: the standard library's smart pointers, which come in two flavors:
std::unique_ptr for resources with a single owner and the team of std::shared_ptr and std::weak_ptr for shared resources. If you're having trouble deciding which your resource is, this FAQ should help you decide. Accessing the raw pointer managed by a smart pointer is as easy as calling its get member function.If you want simple, scope-based resource management,
std::unique_ptr is an excellent tool for the job. It's designed for minimal overhead, and is easy to set up to use custom destruction logic. So easy, in fact, that you can do it when you declare the resource variable:
#include <memory> // allow use of smart pointers
struct CStyleResource; // c-style resource
// resource lifetime management functions
CStyleResource* acquireResource(const char *, char*, int);
void releaseResource(CStyleResource* resource);
// my code:
std::unique_ptr<CStyleResource, decltype(&releaseResource)>
resource{acquireResource("name", nullptr, 0), releaseResource};
acquireResource executes where you call it, at the start of the variable's lifetime. releaseResource will execute at the end of the variable's lifetime, usually when it goes out of scope. You can do much the same with
std::shared_ptr, if you require that brand of resource lifetime instead:
// my code:
std::shared_ptr<CStyleResource>
resource{acquireResource("name", nullptr, 0), releaseResource};
Now, both of these are all well and good, but the standard library has
std::make_unique and std::make_shared and one of the reasons is further exception-safety.GotW#56 mentions that the evaluation of arguments to a function are unordered, which means if you have a function that takes your shiny new
std::unique_ptr type and some resource that might throw on construction, supplying that resource to a function call like this:
func(
std::unique_ptr<CStyleResource, decltype(&releaseResource)>{
acquireResource("name", nullptr, 0),
releaseResource},
ThrowsOnConstruction{});
means that the instructions might be ordered like this:
1. call acquireResource
2. construct ThrowsOnConstruction
3. construct std::unique_ptr from resource pointer
Now our precious C interface resource won't be cleaned up properly if step 2 throws because the
unique_ptr hasn't been constructed yet..Again as mentioned in GotW #56, there's actually a relatively simple way to deal with the exception safety problem. Unlike expression evaluations in function arguments, function evaluations can't be interleaved. So if we acquire a resource and give it to a
unique_ptr inside a function, we'll be guaranteed no tricky business will happen to leak our resource when ThrowsOnConstruction throws on construction. We can't use std::make_unique, because it returns a std::unique_ptr with a default deleter, and we want our own custom flavor of deleter. We also want to specify our resource acquisition function, since it can't be deduced from the type without additional code. Implementing such a thing is simple enough with the power of templates.
#include <memory> // smart pointers
#include <utility> // std::forward
template <
typename T,
typename Deletion,
typename Acquisition,
typename...Args>
std::unique_ptr<T, Deletion> make_c_handler(
Acquisition acquisition,
Deletion deletion,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), deletion};
}
You can use it like this:
auto resource = make_c_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0);
and call func worry-free, like this:
func(
make_c_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0),
ThrowsOnConstruction{});
The compiler can't take the construction of
ThrowsOnConstruction and stick it between the call to acquireResource and the construction of the unique_ptr, so you're good.π5
The
Use is once again similar to the
and
There's a further improvement you can make to the use of
Then you can modify make_c_handler like so:
The usage syntax then changes slightly, to
shared_ptr equivalent is similarly simple: just swap out the std::unique_ptr<T, Deletion> return value with std::shared_ptr<T>, and change the name to indicate a shared resource:
template <
typename T,
typename Deletion,
typename Acquisition,
typename...Args>
std::shared_ptr<T> make_c_shared_handler(
Acquisition acquisition,
Deletion deletion,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), deletion};
}
Use is once again similar to the
unique_ptr version:
auto resource = make_c_shared_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0);
and
func(
make_c_shared_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0),
ThrowsOnConstruction{});
There's a further improvement you can make to the use of
std::unique_ptr: specifying the deletion mechanism at compile time so the unique_ptr doesn't need to carry a function pointer to the deleter when it's moved around the program. Making a stateless deleter templated on the function pointer you're using requires four lines of code, placed before make_c_handler:
template <typename T, void (*Func)(T*)>
struct CDeleter{
void operator()(T* t){Func(t);}
};
Then you can modify make_c_handler like so:
template <
typename T,
void (*Deleter)(T*),
typename Acquisition,
typename...Args>
std::unique_ptr<T, CDeleter<T, Deleter>> make_c_handler(
Acquisition acquisition,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), {}};
}
The usage syntax then changes slightly, to
auto resource = make_c_handler<CStyleResource, releaseResource>(
acquireResource, "name", nullptr, 0);
make_c_shared_handler would not benefit from changing to a templated deleter, as shared_ptr does not carry deleter information available at compile time.π7
ImplementingCVs.pdf
151.9 KB
--
Implementing Condition Variables with Semaphores
The paper discusses the implementation in C# but it is a straightforward translation to C/C++.
The reason I shared this paper is to show the thought process and the things people have to keep in mind when designing a solution for concurrency/parallel programming primitives.
All the kernels provide a semaphore as a primitive that can be used to build all the necessary primitives that a concurrency library provides.
Implementing Condition Variables with Semaphores
The paper discusses the implementation in C# but it is a straightforward translation to C/C++.
The reason I shared this paper is to show the thought process and the things people have to keep in mind when designing a solution for concurrency/parallel programming primitives.
All the kernels provide a semaphore as a primitive that can be used to build all the necessary primitives that a concurrency library provides.
π6
Undefined Behavior Sanitizer for C/C++ explained in depth:
https://maskray.me/blog/2023-01-29-all-about-undefined-behavior-sanitizer
If you want to learn what Undefined Behavior is, you can refer to a post earlier in this channel here.
https://maskray.me/blog/2023-01-29-all-about-undefined-behavior-sanitizer
If you want to learn what Undefined Behavior is, you can refer to a post earlier in this channel here.
MaskRay
All about UndefinedBehaviorSanitizer
Updated in 2025-01. UndefinedBehaviorSanitizer (UBSan) is an undefined behavior detector for C/C++. It consists of code instrumentation and a runtime. Both components have multiple independent impleme
π9
C/C++ Resources and FAQ pinned Β«List of books recommended for learning C (Beginners to Advanced) Beginner: C Programming - A Modern Approach Effective C Beej's Guide to C Programming - Free Online Book Intermediate Modern C 21st Century C - C tips from new school Advanced Linuxβ¦Β»