CPP


https://github.com/yuchdev/CppBooks

RAII

According to chatgpt:

auto variables

Links:

TODO noexcept

Both of these are effectively the same:

void g() noexcept(false);
void g();

TODO mutable

TODO Lambda functions

Format:

[capture](parameters) specifiers -> return_type {
    // function body
};

Examples:

Capture variables

  • Variables from surrounding scope
  • Types of capture variables are not mentioned since compiler already knows about it. Because they are pre-existing names.
  • A lambda, together with its capture variables = closure of the lambda
  • Capture variables get 'stored' inside the lambda object, but arguments are just passed to the lambda
  • Capture variables are useful to 'remember' some variables
  • Syntax:
    • [=]: capture everything (by value)
    • [*]: capture everything (by reference)
    • [=, &x]: capture all by value, x by reference
    • [&, x]: capture everything by reference, x by value

TODO static_cast

Value categories

https://en.cppreference.com/cpp/language/value_category

Primary categories

Every expression has a primary value category associated with it

  • prvalue: pure rvalue
  • xvalue: eXpiring value
  • lvalue: glvalue that is not an xvalue

Mixed categories

  • rvalue: glvalue that is not an lvalue
  • glvalue: generalized lvalue

Template meta-programming (TMP)

An example from the internet. This is supposed to take a day to compile on a dual-core processor with 1GB RAM: https://cpptruths.blogspot.com/2005/11/c-templates-are-turing-complete.html

template<int Depth, int A, typename B>
struct K17 {
static const int x =
K17 <Depth+1, 0, K17<Depth,A,B> >::x
+ K17 <Depth+1, 1, K17<Depth,A,B> >::x
+ K17 <Depth+1, 2, K17<Depth,A,B> >::x
+ K17 <Depth+1, 3, K17<Depth,A,B> >::x
+ K17 <Depth+1, 4, K17<Depth,A,B> >::x;
};
template <int A, typename B>
struct K17 <16,A,B> {
    static const int x = 1;
};
static const int z = K17 <0,0,int>::x;
int main(void) { }

See:

Original program

(Thanks to Kevin for introducing me to this.)

The example program which drew lot of attention to TMP in C++: http://www.erwin-unruh.de/primorig.html

The original version is no longer valid CPP, but a version here works.

#ifndef LAST
#define LAST 18
#endif

enum {
    IS_PRIME,
    NO_PRIME,
    CONTINUE  
};

template <int candidate, int testValue>
struct Eval {
    enum { 
        mode = 
            testValue * testValue > candidate ? IS_PRIME  :    
            candidate % testValue == 0 ?        NO_PRIME  :
                                                CONTINUE
    };
};

template <int candidate, int prime, int mode >
struct sieve {
    enum { 
        next = prime + 1,
        isPrime = sieve<candidate, next, 
                        Eval<candidate, next>::mode>::isPrime
    };
};

template <int candidate, int prime> 
struct sieve<candidate, prime, IS_PRIME> {
    enum { isPrime = IS_PRIME };
};

template <int candidate, int prime>
struct sieve<candidate, prime, NO_PRIME > {
    enum { isPrime = NO_PRIME };
};

template <int prime>
struct test {
    enum {isPrime = sieve<prime, 2, Eval<prime, 2>::mode>::isPrime };
};

template <int prime, int isPrime>
struct show {                           
    static void f() {
        show<prime - 1, test<prime - 1>::isPrime >::f();
    }
};

template <int prime>
struct show<prime, IS_PRIME> {
    static int *f() {
        show<prime - 1, test<prime - 1>::isPrime >::f();

        int x;
        return &x;
    }
};

template <>
struct show<1, IS_PRIME> {
    static void f() {}
};

template <int prime>
void primes() {
    show<prime, test<prime>::isPrime>::f();
}

int main() {
    // 'instantiation' messages because of the suggested grep command
    static_assert(LAST >= 2, 
        "instantiation of LAST must be >= 2");

    primes<LAST>();

    static_assert(0, 
        "instantiation of compilation terminated");
}

Run with:

$ g++ input.cpp -DLAST=30 2>&1 | grep 'instantiation of'

input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 29]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 23]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 19]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 17]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 13]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 11]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 7]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 5]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 3]’:
input.cpp: In instantiation of ‘static int* show<prime, 0>::f() [with int prime = 2]’:

Constraints and concepts (C++20)

Structured binding (C++17)

Allows the constitutent values of a compound type to be assigned to variables.

See:

Unnamed function argso

void foo(int, double)
{
    // The int and double cannot be used in body
    // since they are unnamed.
}

Another example from Boost: https://www.boost.org/doc/libs/latest/doc/html/proto/users_guide.html#boost_proto.users_guide.getting_started.hello_calculator.constructing_expression_trees

template<int I>
double operator()(proto::tag::terminal, placeholder<I>) const
{
    return this->args[I];
}  

'ref-qualifier'

// Only lvalues can call
#include<iostream>

class Foo {
public:
    void hello() & {
        std::cout << "lvalue\n";
    }
};

int main() {
    // Works fine.
    Foo obj;
    obj.hello();

    // Error because of the '&' in function delcaration
    Foo{}.hello();
}

Another one:

// Only rvalues (temporary values) can call
#include<iostream>

class Bar {
public:
    void hello() && {
        std::cout << "rvalue\n";
    }
};

int main() {
    // Error because of the '&&' in function delcaration
    Bar obj;
    obj.hello();

    // Works fine.
    Bar{}.hello();
}

With both versions via function overloading:

#include<iostream>

class Foo {
public:
    void hello() & {
        std::cout << "lvalue\n";
    }
    void hello() && {
        std::cout << "rvalue\n";
    }
};

int main() {
    Foo obj;
    obj.hello();

    Foo{}.hello();
}

copy constructor

operator()

#include<iostream>

struct Twice {
    int operator()(int a) {
        return 2*a;
    }
};

int main() {
    Twice obj;
    std::cout<< obj(5) <<std::endl;  /* 10 */
}

stdlib

regex (C++11)

vector

  • Default value of elements of a vector<T> is 0
#include<iostream>
#include<vector>

int main() {
    std::vector<int> v1 = {1, 2, 3};
    std::cout << v1[0] << std::endl;  // 1

    std::vector<int> v2(3);
    std::cout << v2[0] << std::endl;  // 0

    std::vector<bool> v3(3);
    std::cout << v3[0] << std::endl;  // 0
}

tuple

https://en.cppreference.com/w/cpp/utility/tuple

An example adapted from cppreference:

#include<iostream>
#include<tuple>

int main() {
    std::tuple<int, char, std::string> ics;
    ics = {5, 'a', "hello"};

    std::cout << "ics[0]: " << std::get<0>(ics) << std::endl;
    std::cout << "ics[1]: " << std::get<1>(ics) << std::endl;
    std::cout << "ics[2]: " << std::get<2>(ics) << std::endl;

    // Getting values in a tuple to vars.
    // Types auto-inferred due to the 'auto' use.
    // The is a C++17 feature named 'structured binding'.
    // GCC (>=7 only) needs -std=c++17
    auto [ival, cval, sval] = ics;

    std::cout << std::endl;
    std::cout << "ival: " << ival << std::endl;
    std::cout << "cval: " << cval << std::endl;
    std::cout << "sval: " << sval << std::endl;

    // Could also be done with std::tie for tuple, but is more verbose.
    int ival2;
    char cval2;
    std::string sval2;
    std::tie(ival2, cval2, sval2) = ics;

    std::cout << std::endl;
    std::cout << "ival2: " << ival2 << std::endl;
    std::cout << "cval2: " << cval2 << std::endl;
    std::cout << "sval2: " << sval2 << std::endl;
}

/*
ics[0]: 5
ics[1]: a
ics[2]: hello

ival: 5
cval: a
sval: hello

ival2: 5
cval2: a
sval2: hello
*/

Similar:

type_traits

https://en.cppreference.com/cpp/header/type_traits

Ask questions about type of values at compile time. Useful for TMP.

  • is_copy_constructible_v: check if copy constructor can be used on a type
    • static_assert(std::is_copy_constructible_v<T>, "Needs copying");
  • is_trivially_copy_constructible_v:
  • is_nothrow_copy_constructible_v:

See:

Miscellaneous

Smart pointers

https://en.cppreference.com/cpp/memory#Smart_pointers

See:

unique_ptr

https://en.cppreference.com/cpp/memory/unique_ptr

  • Unique ownership
  • 'owns and manages another object via a pointer'

Note: auto_ptr is apparently something old which should no longer be used.

More

  • shared_ptr
  • weak_ptr

Memory management

Single object

Array of objects:

Type aliases

Can be made with using

using IntRef = int&;

Style and conventions

Position of & and *

  • int& b; ✓
  • int &b; ✗

This is the style followed by gcc.

  • & and * written 'as part of' type
  • Only one variable per declaration

int* a, b;

means only a is a pointer, b is just an ~int. This is why some people prefer to write

int *a, b;

Instead, we can restrict number of variables per declaration to one:

int *a;
int b;

General

  • Indentation level
    • Professional styles say 2 spaces per level
    • 4 spaces should be cool for hobbyists
  • Style guides
    • Google style guide
    • LLVM
  • NQ: is using auto as return type indiscriminately bad?

Linters

  • clang-format
    • clang-format --style=Google

    • clang-format --style=LLVM

    • Config:

      IndentWidth: 4
      UseTab: Never
      
    • Make a fresh user-level config with: clang-format -style=Google -dump-config > ~/.clang-format

constexpr