Keywords from A to C

In this entry I will take a moment to enumarate all the keywords avaliable in C++ (yes, all of them) and give you some examples about how to use all of them and how useful they are in some situations. The article will be splitted in three parts to facilitate the search to the user. You can check the part 2 and part 3.

A

B

C


A

  • alignas: Specifies the alignment in memory for a class, struct, non bit field members and variables. If the alignment specified is smaller than the biggest size element, the program is ill-formed. See: cppreference/alignas
struct alignas(int) X {};
struct alignas(64) Y {};
struct alignas(short) Z {Y y;} // ill-formed, the alignment will be Y (64)
struct alignas(256) S {};

int main() {
    S s;
    std::cout << alignof(s); // Prints "256".
}
  • and: Performs a logic and operation. It is equivalent to &&. See: cppreference/and
if (true and true) {
        std::cout << "It is true";
}
  • and_eq: Performs a bitwise and operation with the value itself. It is equivalent to &=. See: cppreference/end_eq
auto b1 = 0xb11110000;
auto b2 = 0xb00101111;

std::cout << std::hex << b1 << '\n'; // b11110000
b1 and_eq b2;
std::cout << std::hex << b1 << '\n'; // b00100000
// This functions...
int f() {
    int x = 10;
    return x;
}

// ... Is equivalent to this assembly inline code.
asm(R"(
.globl func
    .type func, @function
    f:
    .cfi_startproc
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $10, -4(%rbp)
        movl    -4(%rbp), %eax
        popq    %rbp
        ret
    .cfi_endproc
)");
  • auto: This keyword has different uses but it can be summerize in the affirmation that it deduce at compile time the type of our declarations. The risk in this keyword is to assume default values in c++. For this, string literals come to help. See: cppreference/auto
auto x = 10;
int x = 10;

auto y = 10.5;
double y = 10.5;
// Be careful!! if you want to be a float you have to specify with string literals, because floating pointers in c++ are double by default.
auto z = 10.5f;
float z = 10.5;

// It can be used aswell in generic programming
auto foo(auto x, auto y) {}

B

auto b1 = 0xb11110000;
auto b2 = 0xb00101111;

auto b3 = b1 bitand b2;
std::cout << std::hex << b3 << '\n'; // b00100000
auto b1 = 0xb00110000;
auto b2 = 0xb00101111;

auto b3 = b1 bitor b2;
std::cout << std::hex << b3 << '\n'; // b00111111
  • bool: Boolean type with the size of 1 byte. Can be true or false. See: cppreference/bool
bool boolean = true;
bool boolean2 = false;

std::cout << sizeof(boolean); // 1
int main()
{
    int i = 2;
    switch (i) {
        case 1: std::cout << "1";   //<---- maybe warning: fall through
        case 2: std::cout << "2";
            break;
    }
 
    std::cout << '\n';
 
    for (int j = 0; j < 2; j++) {
        if (j == 1) break;
    }
}

C

  • case: Used only inside a switch statement to define different paths depending on the input value. See: cppreference/case
switch (1)
{
    case 1:
        std::cout << '1'; // prints "1"
        break;       // and exits the switch
    case 2:
        std::cout << '2';
        break;
}
try {
    std::string("abc").substr(10); // throws std::length_error
} catch (const std::exception& e) { // reference to the base of a polymorphic object
    std::cout << e.what(); // information from length_error printed
}
char c = 'h';
std::cout << sizeof(c); // 1
char8_t c = 'h';
std::cout << sizeof(c); // 1
char16_t c = 'h';
std::cout << sizeof(c); // 2
char32_t c = 'h';
std::cout << sizeof(c); // 4
  • class: It can be use to declare a class, an enumeration scoped, as a forward declaration or as a template type parameter. See: cppreference/class
class Foo;  // forward declaration of a class
 
class Bar { // definition of a class
  public:
    Bar(int i) : m_i(i) {}
  private:
    int m_i;
};
 
template <class T> // template argument
void qux() {
    T t;
}
 
enum class Pub { // scoped enum, since C++11
    b, d, p, q
};
 
int main()
{
    Bar Bar(1);
    class Bar Bar2(2); // elaborated type
}
  • compl: Performs a bitwise complement operation. It is equivalent to ~. See: cppreference/compl
auto b1 = 0xb00001111;
auto b2 = compl b1;
std::cout << std::hex << compl b2; // b11110000
  • concept: The way to define constraints to later be applied in templates. In the code snippet below we define a concept that requieres type T to be a floating point in order to success. They are always used along “requieres” See: cppreference/concept
template<typename T>
concept floating = std::is_floating_point<T>::value;

template<typename T>
requires floating<T> 
void foo(T t)  {
    std::cout << "is floating point";
};

int main () {
    foo(10.5);
    foo(10); // Compile error, this integer does not fullfill the floating condition
}
const x = 10;
x = 20; // Compile time error: assignment of read-only variable 'x'
  • consteval: Specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant. It can be used in lambdas, functions and if statements See: cppreference/consteval
consteval int sqr(int n) {
  return n*n;
}
constexpr int r = sqr(100); // OK
 
int x = 100;
int r2 = sqr(x);  // Error: Call does not produce a constant
  • constexpr: It can be used in functions, lambdas and if statements. The constexpr specifier declares that it is possible to evaluate the value of the function or variable at compile time. See: cppreference/constexpr
constexpr int foo() {
    return 42;
}
constexpr int x = foo(); // x = 42, directly at compile time
const char *g() { return "dynamic initialization"; }
constexpr const char *f(bool p) { return p ? "constant initializer" : g(); }
 
constinit const char *c = f(true); // OK
constinit const char *d = f(false); // error: variable 'd' does not have a constant initializer
  • const_cast: Add or removes a const restriction on a variable. Removing const in a variable is undefined behaviour. It recieves a reference to the variable and returns a pointer to a new casted variable. See: cppreference/const_cast
// Remove const
const int x = 10;
int* y = const_cast<int*>(&x);
*y = 20;
std::cout << *y;
// Add const
int j = 3; 
const int* pj = const_cast<const int*>(&j);
*pj = 10; // error: assignment of read-only location '* pj'
for (int i = 0; i < 10; i++) {
    if (i != 5) continue;
    std::cout << i << " ";       //this statement is skipped each time i!=5
}
  • co_await: Suspends a coroutine and returns control to the caller. The expression next to the co_await keyword is converted into an awaitable. It performs an awaitable.operator co_await() if possible, if not, operator co_await(static_cast<Awaitable&&>(awaitable)) for a non-member overload. The awaiter.await_ready() is called, if it returns false then the coroutine is suspended and awaiter.await_suspend(handle) is called. We have different scenarios depending what awaiter.await_suspend(handle) returns:
    • Returns void: Then the action is returnet inmediatly to the caller.
    • Returns true: Returns control to the caller/resumer of the current coroutine.
    • Returns false: Resumes the current coroutine.
    • Returns a coroutine handle for some other coroutine: That handle is resumed (by a call to handle.resume())

Finally, the awaiter.await_resume() is called.

See: cppreference/co_await

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>

struct awaitable {
    std::jthread* p_out;

    bool await_ready() {
        std::cout << "awaitable::await_ready()\n"; // Print 4.
        return false;
    }

    void await_suspend(std::coroutine_handle<> h) {
        std::cout << "awaitable::await_suspend()\n"; // Print 5.
        std::jthread& out = *p_out;
        out = std::jthread([h] { h.resume(); });
        std::cout << "New thread ID: " << out.get_id() << '\n'; // Print 6.
    }

    void await_resume() { 
        std::cout << "awaitable::await_resume()\n"; // Print 7. 
    }
};

struct task {
    struct promise_type {
        task get_return_object() {
            std::cout << "task::promise_type::get_return_object()\n"; // Print 1.
            return {};
        }
        std::suspend_never initial_suspend() { 
            std::cout << "task::promise_type::initial_suspend()\n"; // Print 2.
            return {};
        } 
        void return_void() {
            std::cout << "task::promise_type::return_void()\n"; // Print 9.
        }
        std::suspend_never final_suspend() noexcept { 
            std::cout << "task::promise_type::final_suspend()\n";  // Print 10.
            return {}; 
        }
        void unhandled_exception() {
            std::cout << "task::promise_type::unhandled_exception()\n"; 
        }
    };
};

awaitable switch_to_new_thread(std::jthread& out) {
    return awaitable{&out};
}

task resuming_on_new_thread(std::jthread& out) {
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n'; // Print 3.
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n'; // Print 8.
}

int main() {
    std::jthread out;
    resuming_on_new_thread(out);
}

// Output
task::promise_type::get_return_object()
task::promise_type::initial_suspend()
Coroutine started on thread: 139977331607360
awaitable::await_ready()
awaitable::await_suspend()
New thread ID: 139977331603200
awaitable::await_resume()
Coroutine resumed on thread: 139977331603200
task::promise_type::return_void()
task::promise_type::final_suspend()
  • co_return: We have a coroutine object that contains and manipulates the promise object. When a coroutine begins its execution it performs the following steps:
    • Allocates the coroutine.
    • Copies all function parameters to the coroutine state.
    • Call the constructor for the promise with all the parameters.
    • Calls promise.get_return_object() and store the result in a local variable.
    • Calls promise.initial_suspend()
    • Then when a couroutine reaches co_return it performs the following: Only one of the functions can be defined.
      • Calls promise.return_void()
      • Calls promise.return_value(expr)
    • Finally it calls the promise.final_suspend()

See: cppreference/co_return

struct promise;
struct coroutine : std::coroutine_handle<promise> {
    using promise_type = struct promise;
};

struct promise {
    coroutine get_return_object() {
        std::cout << "promise::get_return_object()\n";
        return {coroutine::from_promise(*this)}; 
    }
    std::suspend_always initial_suspend() noexcept { 
        std::cout << "promise::initial_suspend()\n";
        return {}; 
    }
    std::suspend_always final_suspend() noexcept { 
        std::cout << "promise::final_suspend()\n";
        return {}; 
    }
    auto return_value(auto x) {
        std::cout << "promise::return_value(" << x << ")\n";
        return x;
    }
    void unhandled_exception() {}
};

int main() {
    coroutine h = [](int i) -> coroutine { // make i a coroutine parameter
    co_return i;
  }(42);
 
  h.resume();
  h.destroy();
}

// Output
promise::get_return_object()
promise::initial_suspend()
promise::return_value(42)
promise::final_suspend()
  • co_yield: Yield-expression returns a value to the caller and suspends the current coroutine. It’s equivalent to co_await promise.yield_value(expr) See: cppreference/co_yield
#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>

template <typename T>
struct Generator {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;

    struct promise_type {  // required
        T value_;
        std::exception_ptr exception_;

        Generator get_return_object() {
            std::cout << "Generator::promise_type::get_return_object()\n";
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { 
            std::cout << "Generator::promise_type::initial_suspend()\n";
            return {}; 
        }
        std::suspend_always final_suspend() noexcept { 
            std::cout << "Generator::promise_type::final_suspend()\n";
            return {}; 
        }
        void unhandled_exception() {
            exception_ = std::current_exception();
        }

        template <std::convertible_to<T> From>  // C++20 concept
        std::suspend_always yield_value(From&& from) {
            value_ = std::forward<From>(from);  // caching the result in promise
            std::cout << "Generator::promise_type::yield_value(" << value_ << ")\n";
            return {};
        }
        void return_void() {
            std::cout << "Generator::promise_type::return_void()\n";
        }
    };

    handle_type h_;

    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool() {
        fill();  // The only way to reliably find out whether or not we finished
                 // coroutine, whether or not there is going to be a next value
                 // generated (co_yield) in coroutine via C++ getter (operator
                 // () below) is to execute/resume coroutine until the next
                 // co_yield point (or let it fall off end). Then we store/cache
                 // result in promise to allow getter (operator() below to grab
                 // it without executing coroutine).
        return !h_.done();
    }
    T operator()() {
        fill();
        full_ = false;  // we are going to move out previously cached
                        // result to make promise empty again
        return std::move(h_.promise().value_);
    }

   private:
    bool full_ = false;

    void fill() {
        if (!full_) {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // propagate coroutine exception in called context

            full_ = true;
        }
    }
};

Generator<uint64_t> caller_count() {
    std::cout << "Called first time\n";

    co_yield 0;

    std::cout << "Called second time\n";

    co_yield 1;
}

int main() {
    auto gen = caller_count(); 

    for (int j = 0; gen; j++)
        gen();
}

// Output
Generator::promise_type::get_return_object()
Generator::promise_type::initial_suspend()
Called first time
Generator::promise_type::yield_value(0)
Called second time
Generator::promise_type::yield_value(1)
Generator::promise_type::return_void()
Generator::promise_type::final_suspend()

Check part 2