C++ Language Features Flashcards

These flashcards cover the C++ language features from C++11 - C++20. Footnotes will show which C++ version the feature appeared in.

1
Q

What are coroutines?

C++20

A

Coroutines are special functions that can have their execution suspended and then resumed. C++20’s coroutines are stackless meaning that their state is allocated on the heap (unless optimized by the compiler).

Defining a coroutin means the co_return, co_await, or co_yield keywords must be present in the function body. Under the hood,
co_yield uses co_await

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What is a concept?

C++20

A

A concept is a named compile-tile predicate which contrains types. Constraints model semantic requirements (e.g. type is numeric or hashable). Because constraints are evaluated at compile-time, they can provider more meaningful error messages and runtime safety.

It takes the following form:

template < template-parameter-list >
concept concept-name = constraint-expression;

For a real example, here’s a concept that limits T to signed integrals.

// Limit `T` to both the `integral` constraint and signedness.
template <typename T>
concept signed_integral = integral<T> && std::is_signed_v<T>;

There are lots of ways to use concepts:
* For constraining function parameters
* For constraining auto deduced variables
* For contraining template params
* And many other forms that use the auto keyword and work with lambdas
* Using the requires keyword to specify requirements

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How would you use template syntax to create a lambda?

C++20

A
auto f = []<typename T>(std::vector<T> v) {
  // ...
};

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Give an example of a range-based for loop using a std::vector initializer.

C++20

A
for (auto v = std::vector{1, 2, 3}; auto& e : v) {
  std::cout << e;
}
// prints "123"

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Is is preferred to implicitly capture or explicitly capture this? Give an example.

C++20

A
struct int_value {
  int n = 0;
  auto getter_fn() {
    // BAD:
    // return [=]() { return n; };

    // GOOD:
    return [=, *this]() { return n; };
  }
};

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What would you use to hint to the optimizer that a certain outcome has a high probability of being executed?

C++20

A

This question is getting at the [[likely]] and [[unlikely]] attributes.

For example:

int random = get_random_number_between_x_and_y(0, 3);
if (random > 0) [[likely]] {
  // body of if statement
  // ...
}

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What does the explicit keyword do?

C++20

A

You can describe the explicit keyword in C++ as a language feature that is used to prevent implicit conversions and copy-initialization.

Basic Definition: The explicit keyword in C++ is used before a constructor to prevent the compiler from using that constructor for implicit conversions. It means that the constructor is only callable if the programmer explicitly calls it.

Preventing Implicit Conversions: Without explicit, C++ allows constructors that can be called with a single argument to be used for implicit type conversions. This might lead to unexpected behavior if the compiler automatically converts types using these constructors. By marking a constructor as explicit, you ensure that such conversions are only made when the programmer explicitly requests it, improving code clarity and safety.

Copy Initialization: explicit also applies to preventing copy initialization. For instance, if you have explicit MyClass(int x);, then writing MyClass obj = 10; would be disallowed, whereas MyClass obj(10); or MyClass obj = MyClass(10); would be fine.

Good Practice: Using explicit is generally considered good practice for single-argument constructors (and in C++11 and later, for constructors that can be called with one argument), as it makes the programmer’s intentions clear and prevents accidental type conversions that can lead to subtle bugs.

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Give an example of bringing an enum’s members into scope to improve readability in a switch statement.

C++20

A

Use the using enum <my_enum> syntax:

enum class rgba_color_channel { red, green, blue, alpha };

std::string_view to_string(rgba_color_channel my_channel) {
  switch (my_channel) {
    using enum rgba_color_channel;
    case red:   return "red";
    case green: return "green";
    case blue:  return "blue";
    case alpha: return "alpha";
  }
}

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Explain all terms in the following section of code:

template <typename... Args>
auto f(Args&&... args){
    // BY VALUE:
    return [...args = std::forward<Args>(args)] {
        // ...
    };
}

C++20

A

This piece of code demonstrates capturing a parameter pack in a lambda expression.

The function f is a template function that accepts a variadic number of arguments. typename... Args defines a parameter pack named Args that can take any number and type of arguments. Args&&... args uses forwarding references (also known as universal references), which means it can bind to both lvalues and rvalues.

Inside the function, std::forward<Args>(args)... is used. This is an example of perfect forwarding. It forwards the arguments to another function or a lambda while preserving their value category (lvalue or rvalue). It’s essential in a variadic template to maintain the original type and value category of each argument.

The lambda is defined inside the function f. Lambdas can capture variables from their enclosing scope, and in this case, the lambda is capturing variables from the parameter pack. Inside the lambda, you can use these captured arguments. Since they are captured by value (after being perfectly forwarded), the lambda has its own copies, and their original type and value category are preserved at the point of capture.

This is particularly useful in template metaprogramming and when writing generic code that needs to handle an arbitrary number of arguments in a type-safe manner.

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

How would you capture parameter packs by reference using a lambda?

C++20

A

Here’s an example of capturing parameter packs by reference using a lambda:

template <typename... Args>
auto f(Args&&... args){
    // BY REFERENCE:
    return [&...args = std::forward<Args>(args)] {
        // ...
    };
}

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What does the constinit specifier do?

C++20

A

The constinit specifier requires that a variable must be initialized at compile-time.

const char* g() { return "dynamic initialization"; }
constexpr const char* f(bool p) { return p ? "constant initializer" : g(); }

// OK:
constinit const char* c = f(true);

// ERROR: `g` is not constexpr, so `d` cannot be evaluated at compile-time.
constinit const char* d = f(false); 

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Explain the following core language Concepts added in C++20 standard library:

  • same_as
  • derived_from
  • convertible_to
  • common_with
  • integral
  • default_constructible

C++20

A
  • same_as - specifies two types are the same
  • derived_from - specifies that a type is derived from another type
  • convertible_to - specifiers that a type is implicitly convertible to another type
  • common_with - specifies that two types share a common type
  • integral - specifiers that a type is an integral type (e.g. int, char, unsigned long, uint32_t)
  • default_constructible - specifies that an object of a type can be default-constructed

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What comparison Concepts have been added to the standard library for C++20?

C++20

A

boolean - specifies that a type can be used in Boolean contexts
equality_comparable - specifies that operator== is an equivalence relation

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Explain the following object Concepts added to the standard library for C++20.

  • movable
  • copyable
  • semiregular
  • regular

C++20

A
  • movable - specifies that an object of a type can be moved and swapped
  • copyable - specifies that an object of a type can be moved, swapped, and copied
  • semiregular - specifies that an object of a type can be moved, swapped, copied, and default constructed
  • regular - specifies that a type is regular meaning is is both semiregular and equality_comparable

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

What callable Concepts were added to the standard library for C++20?

C++20

A
  • invocable - specifies that a callable type can be invoked with a given set of argument types
  • predicate - specifies that a callable type is a Boolean predicate

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What is buffering in the context of output streams? How does synchronization relate?

C++20 Synchronized Buffered OutputStream

A

Buffering refers to storing data in a temporary memory area (a buffer) before sending it to the final destination (e.g. files, network sockets). This approach can enhance performance, especially for I/O operations, by reducing the number of calls to the underlying system for each output operation. Data is accumulated in the buffer and written in larger chunks.

Synchronization in this context refers to making the output operations thread-safe. In a multithreaded environment, different threads may attempt to write to the same output stream simultaneously. Without synchronization to ensure only one thread performs the output operation at a time, these operations could interleave, resulting in corrupted or jumbled output.

C++20 Synchronized Buffered OutputStream

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

What is the following code doing?

std::osyncstream{std::cout} << "The value of x is:" << x << std::endl;

C++20 Synchronized Buffered OutputStream

A

This code buffers the output operations for the wrapped output stream and ensures synchronization so that there’s no interleaving of the output.

C++20 Synchronized Buffered OutputStream

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Since std::span doesn’t propogate const, how would you construct a read-only span?

C++20

A

To construct a read-only span, you can use the const qualifier when constructing the span like so: std::span<const T>

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

What is the purpose of std::span?

C++20

A

A span is a type of view (i.e. non-owning) of a container that provides bounds-checked access to a contiguous group of elements. Views to not own their elements, more like holding references to their data, so they are cheap to construct and copy. Instead of having to maintain a pointer/iterator and a length field representing the number of elements, a span wraps both of those into a single object. Spans can be dynamically-sized or fixed-sized. A fixed-sized span will fail to compile for containers that don’t match the extent (i.e. specified size) of the span.

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Why does the following code produce an error?

void print_three_ints(std::span<const int, 3> ints) {
    for (const auto n : ints) {
        std::cout << n << std::endl;
    }
}

print_three_ints(std::vector{ 1, 2, 3 }); // ERROR

C++20

A

We’re passing a temporary object, std::vector{...}, to the function which means there’s an implicit conversion from std::vector to std::span<const int, 3>. This doesn’t compile because std::span` with a fixed size expects the source to match the size exactly.

To fix this error, we can take two approaches:
1. Change the function signature to void print_three_ints(std::span<const int> ints) to remove the fixed size
2. Explicitly create a span with the appropriate size like:

std::vector<int> vec = { 1, 2, 3 };
std::span<const int, 3> span(vec.begin(), vec.end());
print_three_ints(span);

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

C++20 adds a new <bit> header. What function would I use to count the number of 1 bits in an unsigned integer?

C++20

A

You can use std::popcount for this: std::popcount(0b1111'0000u); // 4

See the docs here.

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Using std::string, how can you tell if a string starts or ends with a provided substring?

C++20

A

You can use the new starts_with and ends_with member functions. This works with strings and string views in C++20.

std::string str = "foobar";
str.starts_with("foo"); // true
str.ends_with("baz"); // false

C++20

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

What’s the alternative for using the “find and check end of iterator” idiom in C++20?

C++20

A

Thank goodness for this one! It’s been a long time coming.

You can now use the contains member function to check if an element is contained in associative containers (e.g. sets, maps).

std::map<int, char> map {{1, 'a'}, {2, 'b'}};
map.contains(2); // true
map.contains(123); // false

std::set<int> set {1, 2, 3};
set.contains(2); // true

C++20

24
Q

What’s the new way to perform bit-wise type covnersions between two objects of different types?

C++20

A

C++20 introduces std::bit_cast for this purpose. It’s meant to be a safe and efficient way to convert types and should be used instead of c-style casts or reinterpet_cast for reinterpreting the bits of a value.

float f = 3.14f;
// Reinterpret the bits of float as an unsigned int
unsigned int bits = std::bit_cast<unsigned int>(f);

C++20

25
Q

What new standard library function can help calculate the midpoint of two integers safely and without overflow?

C++20

A

You can use std::midpoint for this purpose.
std::midpoint(1, 3); // == 2

C++20

26
Q

What does std::to_array do?

C++20

A

Converts a given array/”array-like” object (sometimes referred to as a built-in array) to a std::array.

Examples:

std::to_array("foo"); // returns `std::array<char, 4>`
std::to_array<int>({1, 2, 3}); // returns `std::array<int, 3>`

int a[] = {1, 2, 3};
std::to_array(a); // returns `std::array<int, 3>`

This provides access to standard container functionalies like size(), allows for type deducation and automatic size, allows for constexpr to be used.

C++20

27
Q

Explain what folding expressions do and give an example.

C++17

A

Folding expressions allow you to apply a binary operator to a pack of template arguments. There are two types of folding:
* Unary folding: Applies an operator between each element of the pack and an initial value.
* Binary folding: Applies an operator between all elements of the pack.

template <typename... Args>
bool logicalAnd(Args... args) {
    // Binary folding.
    return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template <typename... Args>
auto sum(Args... args) {
    // Unary folding.
    return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0

C++17

28
Q

Give an example of a compile-tile lambda using constexpr.

C++17

A
constexpr int addOne(int n) {
  return [n] { return n + 1; }();
}

static_assert(addOne(1) == 2);

C++17

29
Q

Prior to C++17, capturing this in a lambda’s environment was reference-only. Why might that be problematic and what did C++17 introduce to fix the problem?

C++17

A

That could be problematic in asynchronous code using callbacks that require the object to be available, potentially past the object’s lifetime. Using [*this] in C++17 will now make a copy of the current object to mitigate the problem.

C++17

30
Q

What is a translation unit in C++?

C++ General

A

A translation unit is a fundamental concept in the compilation process. It represents a single source file after it has been processed by the preprocessor. It includes the original source code, the code from included headers, and the expanded macro definitions.

C++ General

31
Q

What does the inline specifier do?

C++17

A

The inline specifier recommends to the compiler that the function call can be replaced with the function’s body to avoid overhead of a function call. It acts as an optimization. The inline specifier was around before C++17, but in C++17 the concept was extended to variables.

C++17

32
Q

How could you simplify the nested namespace code below?

namespace A {
  namespace B {
    namespace C {
      int i;
    }
  }
}

C++17

A

You could simplify it like this:
~~~
namespace A::B::C { int i; }
~~~

C++17

33
Q

Structured bindings were introduced in C++17 to allow for convenient and readable unpacking of tuples, pairs, arrays, and structs into separate variables.

How would you use this to access the coordinate points?

using Coordinate = std::pair<int, int>;
Coordinate origin() {
  return Coordinate{0, 0};
}

C++17

A

You can access the coordinate points like this:

const auto [ x, y ] = origin();
x; // == 0
y; // == 0

C++17

34
Q

Structured bindings allow destructuring by reference. How would you use that to access the key and value for each item in mapping?

std::unordered_map<std::string, int> mapping {
  {"a", 1},
  {"b", 2},
  {"c", 3}
};

C++17

A

You can access the key and values like this:

// Destructure by reference.
for (const auto& [key, value] : mapping) {
    // Do something with key and value
}

C++17

35
Q

Explain what the following piece of code is doing:

{
  std::lock_guard<std::mutex> lk(mx);
  if (v.empty()) v.push_back(val);
}

C++17

A

This is an example of the simplified if statement new in C++17. This code is using std::lock_guard, a mutex wrapper that provides an RAII-style mechanism for owning a mutex for the duration of the scope block. When the first line is executed, lk locks the mutex, ensuring no other thread can enter the block. The code adds an element to the vector, v, if v is empty. At the end of the block, the lock_guard object (lk) goes out of scope. The destructor is called which releases the mutex so that other threads are now free to acquire it.

C++17

36
Q

What is if constexpr used for?

C++17

A

In C++17, the if constexpr construct was introduced so that it conditional statements could be evaluated at compile-time.

One of the features of if constexpr is that the compiler discards the branch that does not get executed based on the condition. This means that if the condition is false, the code inside that if block is not just skipped at runtime; it’s as if it doesn’t exist in the compiled program.

template <typename T>
auto getValue(T t) {
    if constexpr (std::is_pointer<T>::value) {
        return *t; // Handle pointer types
    } else {
        return t; // Handle non-pointer types
    }
}

C++17

37
Q

Initialize two “bytes” that will work and two that will cause an error based on this enum definition:

enum byte : unsigned char {};

C++17

A
byte b {0};         // OK
byte d = byte{1};   // OK
byte c {-1};        // ERROR
byte e = byte{256}; // ERROR - unsigned char has a range of 0-255 typically

After C++11, list initialization (i.e. using curly braces) prohibits narrowing conversions so {256} will not be converted.

C++17

38
Q

Explain these new attributes addded in C++17:

  • [[ fallthrough ]]
  • [[ nodiscard ]]
  • [[ maybe_unused ]]

C++17

A
  • [[ fallthrough ]] - indicates to the compiler that falling through in a switch statement is intended behavior
  • [[ nodiscard ]] - issues a warning when either a function or class has this attribute and its return value is discarded
  • [[ maybe_unused ]] - indicates to the compiler that a variable or parameter might be unused and is intentionally so

C++17

39
Q

Give two primary examples of how the \_\_has_include operator can be used in #if and #elif expressions.

C++17

A

The \_\_has_include can be used for the following cases:
1. If trying to use two librairies that work the same way, keep several options for which to use in case one is not found.
2. If trying to include headers that may exist under different names or locations on various platforms.

Example of #1
~~~
#ifdef __has_include
# if __has_include(<optional>)
# include <optional>
# define have_optional 1
# else
# define have_optional 0
# endif
#endif
~~~</optional></optional>

Example of #2
~~~
#ifdef __has_include
# if __has_include(<OpenGL/gl.h>)
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
# elif __has_include(<GL/gl.h>)
# include <GL/gl.h>
# include <GL/glu.h>
# else
# error No suitable OpenGL headers found.
# endif
#endif
~~~

C++17

40
Q

Explain what the compiler will deduce from the constructor arguments because of class template argument deduction.
~~~
std::vector v{ 1, 2, 3 };

std::mutex mtx;
auto lck = std::lock_guard{ mtx };

auto p = new std::pair{ 1.0, 2.0 };
~~~

C++17

A
// deduces std::vector<int>
std::vector v{ 1, 2, 3 }; 

std::mutex mtx;
// deduces to std::lock_guard<std::mutex>
auto lck = std::lock_guard{ mtx };

// deduces to std::pair<double, double>
auto p = new std::pair{ 1.0, 2.0 };

C++17

41
Q

What is std::variant used for?

C++17

A

std::variant is a type-safe union, offering a way to store one of several possible types in a single object. The value can be one of several types, but only one at a time.

This could be useful for several reasons:
* Unlike traditional unions, std::variant ensures type safety
* For state machines, parsers, or systems handling various message types
* If a function can return multiple types, this can be used instead of void*
* Works well with the visitor pattern, allowing actions to be performed based on the variant’s current type. This is facilitated by std::visit, which can apply a function or a lambda to the contained value, handling each possible type.
* Serialization/deserialization where an object can be one of several types based on some external input (like a JSON or XML file)

Simple Example
~~~
std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0
~~~</double></int>

C++17

42
Q

What return type might you use if you have a function that could return either a std::string or no string?

C++17

A

For this, using std::optional is a good idea. std::optional is a template class that represents a value that may or may not be present. If you return { } from the function, the return type will be returning std::optional<std::string> which is a “nullopt” state (has no value).

std::optional<std::string> create(bool b) {
  if (b) {
    return "Godzilla";
  } else {
    return {};
  }
}

create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
  // ...
}

C++17

43
Q

What do std::any and std::any_cast do? What should you consider before using them?

C++17

A

std::any can hold an instance of any type. For example: std::any val = 1; // contains an int. The type of the value may not be know until runtime, or may vary.

std::any_cast is used to retrieve the value from a std::any object. Type safety is ensured at runtime.

Things to consider:
* There’s a performance cost due to dynamic memory allocation and type erasure overhead. Type erause is when the specific type of information of the stored object is erased and the object is treated generically as part of std::any.
* You could use std::variant if you have several known types which would avoid the type erasure overhead.
* The type safety of std::any_cast is runtime checked which is much less preferable than compile-time checks.

C++17

44
Q

What is a std::string_view and how could you use it to remove all the spaces in the beginning of the string: “ trim me”?

C++17

A

A std::string_view is a non-owning reference to a string which can be used as an abstraction on top of strings (e.g. parsing). We’ll parse the string “ trim me” using a std::string_view like this:

std::string str {"     trim me"}
std::string_view str_view {str};
str_view.remove_prefix(std::min(str_view.find_first_not_of(" "), str_view.size( )));
str_view; // "trim me"

Note that in the example above, we use std::min. This is necessary for the case where the string is made up of the values that we are trying to remove. In that case, find_first_not_of would return std::string::npos which is a constant representing a an invalid position. The std::min allows us to return the size of the string instead of having an error here.

C++17

45
Q

Explain the following code example, specifically:
* What is this class Proxy for?
* The use of std::invoke

template <typename Callable>
class Proxy {
  Callable c_;

public:
  Proxy(Callable c) : c_{ std::move(c) } {}

  template <typename... Args>
  decltype(auto) operator()(Args&&... args) {
    // ...
    return std::invoke(c_, std::forward<Args>(args)...);
  }
};

C++17

A

The Proxy class acts a generic wrapper for any callable object (e.g. function pointer, lambda expression, functor). The class stores any callable and forwards calls to it.

The constructor of Proxy takes a callable object c and moves it into the member variable _c using std::move which is efficient and avoids unnecessary copying.

The class overloads the function call opertor, operator, with a template that allows it to accept any number and type of arguments by using Args&&... args. The call operator uses std::invoke to call the stored callable _c with arguments forwarded using std::forward<Args>(args) which preserves their value category (rvalue or lvalue).

The return type of the call operator is decltype(auto), which is a way of saying “deduce the return type based on the return type of the expression inside the function.”

C++17

46
Q

How would you invoke a Callable with a tuple of arguments?

C++17

A

You can use std::apply to invoke a Callable object with a typle of arguments.

auto add = [](int x, int y) {
   return x + y;
}
std::apply(add, std::make_tuple(1, 2));

C++17

47
Q

What new standard library provides ways to manipulate files, directories, and paths in a filesystem?

C++17

A

You can use the std::filesystem library for this.

const auto bigFilePath {"bigFileToCopy"};
if (std::filesystem::exists(bigFilePath)) {
  const auto bigFileSize {std::filesystem::file_size(bigFilePath)};
  std::filesystem::path tmpPath {"/tmp"};
  if (std::filesystem::space(tmpPath).available > bigFileSize) {
    std::filesystem::create_directory(tmpPath.append("example"));
    std::filesystem::copy_file(bigFilePath, tmpPath.append("newFile"));
  }
}

C++17

48
Q

What standard library type can you use such that only operator overloads available are bitwise operators?

C++17

A

You can use std::byte for this.

std::byte a {0};
std::byte b {0xFF};
int i = std::to_integer<int>(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer<int>(c); // 0

C++17

49
Q

How would you change the key of a map element efficiently?

C++17

A

You can use extract, insert, and move semantics like this:

std::map<int, string> m {{1, "one"}, {2, "two"}, {3, "three"}};
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }

C++17

50
Q

What does std::sample do?

C++17

A

Samples n elements in a given sequence (without replacement) where every element has an equal chance of being selected.

const std::string ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::string guid;
// Sample 5 characters from ALLOWED_CHARS.
std::sample(ALLOWED_CHARS.begin(), ALLOWED_CHARS.end(), std::back_inserter(guid),
  5, std::mt19937{ std::random_device{}() });

std::cout << guid; // e.g. G1fW2

C++17

51
Q

How would you fix a value between a lower and an upper bound?

C++17

A

You can use std::clamp to fix a value between a lower and upper bound.

std::clamp(42, -1, 1); // == 1
std::clamp(-42, -1, 1); // == -1
std::clamp(0, -1, 1); // == 0

// `std::clamp` also accepts a custom comparator:
std::clamp(0, -1, 1, std::less<>{}); // == 0

C++17

52
Q

What are the three smart pointers introduced in C++11? Give a short description of each.

C++11

A

These are:
1. std::unqiue_ptr - a non-copyable, movable pointer that manages its own heap-allocated memory
2. std::shared_ptr - a smart pointer that manages a resource shared across multiple owners. A shared pointer holds a control block which contains a few components (e.g. managed object, reference counter). Control block access is thread-safe. However, manipulating the managed object is not thread-safe.
3. std::weak_ptr - a smart pointer that holds a non-owning (“weak”) reference to an object that is managed by std::shared_ptr

C++11

53
Q

Why is using std::make_unique the recommended way of creating instances of std::unique_ptr?

C++11/17

A

Using std::make_unique means you can avoid using the new operator. It also provides exception-safety and prevents code duplication for specifying the underlying type the pointer shall hold.

Example:
foo(std::make_unique<T>(), function_that_throws(), std::make_unique<T>());

As compared to:
foo(std::unique_ptr<T>{new T{}}, function_that_throws(), std::unique_ptr<T>{new T{}});
This method creates T on the heap, THEN is calls the function that throws an exception which means we may be introducing a memory leak here.

C++11/17

54
Q

Why is using std::make_shared the recommended way of creating instances of std::shared_ptr?

C++11/17

A

Using std::make_shared avoids having to use the new operator. It prevents code duplication when specificaly the underlying type the pointer holds. It also provides exception-safety.

Example:
foo(std::make_shared<T>(), function_that_throws(), std::make_shared<T>());

Compared to:
foo(std::shared_ptr<T>{new T{}}, function_that_throws(), std::shared_ptr<T>{new T{}});

In addition to avoiding a potential memory leak, using make_shared rather than std::shared_ptr{ new T{} } prevents having to do two allocations (one for allocating memory for T and than the shared pointer memory allocation for the control block within the pointer).

C++11/17

55
Q

Explain the difference between r-values and l-values.

C++11

A

Modern definition (still somewhat nebulous):
An lvalue is an expression that refers to a memory location and allows us to take the address of that memory location via the & operator. An rvalue is an expression that is not an lvalue.

Outdated definition:
An lvalue is an expression that may appear on the left or on the right hand side of an assignment. An rvalue is an expression that can only appear on the right hand side of an assignment.

Examples:

  // lvalues:
  //
  int i = 42;
  i = 43; // ok, i is an lvalue
  int* p = &i; // ok, i is an lvalue
  int& foo();
  foo() = 42; // ok, foo() is an lvalue
  int* p1 = &foo(); // ok, foo() is an lvalue

  // rvalues:
  //
  int foobar();
  int j = 0;
  j = foobar(); // ok, foobar() is an rvalue
  int* p2 = &foobar(); // error, cannot take the address of an rvalue
  j = 42; // ok, 42 is an rvalue

http://thbecker.net/articles/rvalue_references/section_01.html

C++11