01 — Language Fundamentals¶
Deep C++ syntax reference covering modern C++17 features used in competitive programming and system-level code. Includes auto/decltype, references, move semantics, templates, lambdas, type traits, smart pointers, and more.
auto and decltype¶
auto deduces the type at compile time from the initializer. decltype extracts the type of an expression without evaluating it.
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"s; // std::string (with using namespace std::literals)
// decltype preserves references and cv-qualifiers
int a = 10;
decltype(a) b = a; // int
decltype((a)) c = a; // int& (extra parens -> lvalue reference)
// auto in function return (C++14)
auto add(int x, int y) { return x + y; }
// decltype(auto) — preserves exact type including reference
decltype(auto) get_ref(int& x) { return x; } // returns int&, not int
References and Move Semantics¶
C++ has three value categories that matter in practice: lvalues (named objects), rvalues (temporaries), and xvalues (movable named objects).
int a = 5;
int& lref = a; // lvalue reference — aliases a
int&& rref = 10; // rvalue reference — binds to temporary
// std::move casts to rvalue — enables move constructor/assignment
std::string s1 = "hello";
std::string s2 = std::move(s1); // s1 is now valid-but-unspecified
// std::forward — perfect forwarding in templates
template<typename T>
void wrapper(T&& arg) {
sink(std::forward<T>(arg)); // preserves lvalue/rvalue category
}
// Move constructor / move assignment
struct Buffer {
int* data;
int sz;
Buffer(int n) : data(new int[n]), sz(n) {}
// Move constructor — steal the resource
Buffer(Buffer&& o) noexcept : data(o.data), sz(o.sz) {
o.data = nullptr; // leave source in destructible state
o.sz = 0;
}
// Move assignment
Buffer& operator=(Buffer&& o) noexcept {
if (this != &o) {
delete[] data;
data = o.data; sz = o.sz;
o.data = nullptr; o.sz = 0;
}
return *this;
}
~Buffer() { delete[] data; }
};
Structured Bindings (C++17)¶
Decompose aggregates, pairs, tuples, and arrays into named variables in a single declaration.
// pair
auto [first, second] = std::make_pair(1, 'a');
// tuple
auto [x, y, z] = std::make_tuple(1, 2.0, "hi");
// struct / aggregate
struct Point { int x, y; };
Point p{3, 4};
auto [px, py] = p;
// map iteration — the idiomatic C++17 way
std::map<std::string, int> freq;
for (auto& [key, val] : freq) {
val++; // can modify through reference
}
// array
int arr[3] = {1, 2, 3};
auto [a, b, c] = arr;
constexpr and if constexpr¶
constexpr forces evaluation at compile time when possible. if constexpr eliminates dead branches at compile time inside templates.
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int f5 = factorial(5); // computed at compile time: 120
// if constexpr — branch discarded at compile time (no instantiation)
template<typename T>
auto stringify(T val) {
if constexpr (std::is_integral_v<T>)
return std::to_string(val);
else
return std::string(val); // only compiled when T is string-like
}
Templates and SFINAE¶
Templates generate code for any type. SFINAE (Substitution Failure Is Not An Error) is the mechanism for compile-time overload selection.
// Function template
template<typename T>
T max_val(T a, T b) { return a > b ? a : b; }
// Class template with default parameter
template<typename T, typename Alloc = std::allocator<T>>
class MyVec { /* ... */ };
// Non-type template parameter
template<int N>
struct Array { int data[N]; };
// SFINAE with std::enable_if
template<typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T next_pow2(T n) {
T p = 1;
while (p < n) p <<= 1;
return p;
}
// C++17: cleaner with if constexpr (prefer over SFINAE when possible)
template<typename T>
void print_type(T v) {
if constexpr (std::is_integral_v<T>) std::cout << "int: " << v;
else if constexpr (std::is_floating_point_v<T>) std::cout << "float: " << v;
else std::cout << "other: " << v;
}
Fold Expressions (C++17)¶
Reduce variadic template parameter packs with a binary operator in a single expression.
// Unary right fold: (pack op ...)
template<typename... Args>
auto sum(Args... args) { return (args + ...); }
// Unary left fold: (... op pack)
template<typename... Args>
auto product(Args... args) { return (... * args); }
// Binary right fold with initial value
template<typename... Args>
void print_all(Args... args) { ((std::cout << args << ' '), ...); }
// Logical folds
template<typename... Args>
bool all_positive(Args... args) { return ((args > 0) && ...); }
int main() {
std::cout << sum(1, 2, 3, 4); // 10
std::cout << all_positive(1,2,-3); // 0
print_all("a", 42, 3.14); // a 42 3.14
}
Lambdas — Deep Dive¶
Lambdas are anonymous function objects (closures). They capture local variables and can be stored, passed, and returned.
// Basic syntax: [capture](params) specifiers -> return_type { body }
auto add = [](int a, int b) { return a + b; };
// Capture modes
int x = 10, y = 20;
auto by_val = [x, y]() { return x + y; }; // copy
auto by_ref = [&x, &y]() { return x + y; }; // reference
auto all_val = [=]() { return x + y; }; // all by copy
auto all_ref = [&]() { return x + y; }; // all by reference
auto mixed = [=, &x]() { return x + y; }; // x by ref, rest by copy
// mutable — allows modifying copies
int cnt = 0;
auto counter = [cnt]() mutable { return ++cnt; };
// Generic lambda (C++14) — templated operator()
auto gmax = [](auto a, auto b) { return a > b ? a : b; };
// Constexpr lambda (C++17)
auto csq = [](int n) constexpr { return n * n; };
static_assert(csq(5) == 25);
// IIFE — Immediately Invoked Function Expression
int result = [&]() {
if (x > 5) return x * 2;
return x + 1;
}();
// Recursive lambda via std::function (slower) or explicit template
std::function<int(int)> fib = [&](int n) -> int {
return n <= 1 ? n : fib(n-1) + fib(n-2);
};
// Faster recursive lambda (C++23 deducing this, or pass-by-ref trick)
auto fib2 = [](auto& self, int n) -> int {
return n <= 1 ? n : self(self, n-1) + self(self, n-2);
};
// call: fib2(fib2, 10)
// Lambda in STL
std::vector<int> v = {5, 3, 1, 4, 2};
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; }); // desc
Type Traits¶
<type_traits> provides compile-time introspection into types. Essential for generic programming and SFINAE.
#include <type_traits>
// Value traits (all end in _v in C++17)
static_assert(std::is_integral_v<int>);
static_assert(std::is_floating_point_v<double>);
static_assert(std::is_pointer_v<int*>);
static_assert(std::is_reference_v<int&>);
static_assert(std::is_same_v<int, int>);
static_assert(std::is_base_of_v<Base, Derived>);
static_assert(std::is_convertible_v<int, double>);
// Type transformations (all end in _t in C++17)
std::remove_const_t<const int> // int
std::remove_reference_t<int&> // int
std::add_pointer_t<int> // int*
std::decay_t<int[3]> // int*
std::conditional_t<true, int, double> // int
// Common patterns in CP
template<typename T>
using IsInt = std::enable_if_t<std::is_integral_v<T>, bool>;
template<typename T, IsInt<T> = true>
T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
Variadic Templates¶
Templates that accept any number of type arguments. Power behind std::tuple, std::variant, and perfect-forwarding wrappers.
// Recursive variadic print
void print() {} // base case
template<typename Head, typename... Tail>
void print(Head h, Tail... t) {
std::cout << h;
if constexpr (sizeof...(t) > 0) std::cout << ", ";
print(t...);
}
// sizeof... — number of elements in pack
template<typename... Args>
constexpr int count() { return sizeof...(Args); }
// Parameter pack expansion in initializer list (C++17 fold preferred)
template<typename... Args>
std::vector<int> make_vec(Args... args) {
return {static_cast<int>(args)...};
}
// Variadic class template (tuple-like)
template<typename... Ts> struct TypeList {};
using MyTypes = TypeList<int, double, std::string>;
__builtin_* Functions and __int128¶
GCC built-ins provide fast bit operations and access to hardware instructions. __int128 extends integer range to 128 bits.
// Bit counting and scanning
__builtin_popcount(x) // count set bits in unsigned int
__builtin_popcountll(x) // count set bits in unsigned long long
__builtin_clz(x) // count leading zeros (UB if x==0)
__builtin_ctz(x) // count trailing zeros (UB if x==0)
__builtin_parity(x) // parity (XOR of all bits)
// Practical usage
int msb(int x) { return 31 - __builtin_clz(x); } // most significant bit position
bool is_pow2(int x) { return x > 0 && __builtin_popcount(x) == 1; }
// __int128 — range: -(2^127) to 2^127 - 1
__int128 big = (__int128)1e18 * 1e18;
// I/O for __int128 (no direct cout support)
void print128(__int128 x) {
if (x < 0) { std::cout << '-'; x = -x; }
if (x > 9) print128(x / 10);
std::cout << (char)('0' + x % 10);
}
// __int128 multiplication mod — avoids overflow when mod is ~1e18
__int128 mulmod(__int128 a, __int128 b, __int128 m) {
return a * b % m;
}
Operator Overloading¶
Define custom behavior for built-in operators on user-defined types.
struct Vec2 {
double x, y;
// Arithmetic (return by value)
Vec2 operator+(const Vec2& o) const { return {x+o.x, y+o.y}; }
Vec2 operator-(const Vec2& o) const { return {x-o.x, y-o.y}; }
Vec2 operator*(double s) const { return {x*s, y*s}; }
// Compound assignment
Vec2& operator+=(const Vec2& o) { x+=o.x; y+=o.y; return *this; }
// Comparison (for use in sets/maps)
bool operator<(const Vec2& o) const {
return x != o.x ? x < o.x : y < o.y;
}
bool operator==(const Vec2& o) const { return x==o.x && y==o.y; }
// Subscript
double& operator[](int i) { return i==0 ? x : y; }
// Output stream (friend — non-member)
friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << '(' << v.x << ',' << v.y << ')';
}
};
// Custom comparator struct for priority_queue / sort
struct CmpBySecond {
bool operator()(const std::pair<int,int>& a,
const std::pair<int,int>& b) const {
return a.second > b.second; // min-heap by second element
}
};
std::priority_queue<std::pair<int,int>,
std::vector<std::pair<int,int>>,
CmpBySecond> pq;
Spaceship Operator <=> (C++20)¶
The three-way comparison operator generates all six comparison operators automatically.
#include <compare>
struct Point {
int x, y;
// auto deduces return type: strong_ordering, weak_ordering, partial_ordering
auto operator<=>(const Point& o) const = default;
// Generates ==, !=, <, <=, >, >= for free
};
// Manual three-way comparison
struct Ratio {
int num, den;
auto operator<=>(const Ratio& o) const {
// cross-multiply to compare without floating point
return (long long)num * o.den <=> (long long)o.num * den;
}
bool operator==(const Ratio&) const = default;
};
Smart Pointers¶
RAII-based ownership semantics. Prefer smart pointers over raw new/delete in all non-CP code.
#include <memory>
// unique_ptr — sole ownership, zero overhead vs raw pointer
auto up = std::make_unique<int>(42);
std::cout << *up; // dereference
up.reset(); // explicitly release
// Unique ptr to array
auto arr = std::make_unique<int[]>(10);
arr[0] = 5;
// shared_ptr — reference-counted shared ownership
auto sp1 = std::make_shared<std::string>("hello");
auto sp2 = sp1; // both own the string
std::cout << sp1.use_count(); // 2
// weak_ptr — non-owning observer, breaks cycles
std::weak_ptr<std::string> wp = sp1;
if (auto locked = wp.lock()) { // lock() returns shared_ptr or nullptr
std::cout << *locked;
}
// Custom deleter
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
fopen("test.txt", "r"), &fclose);
C++ Casts¶
Four named casts replace C-style (Type)expr for safer, searchable type conversions.
// static_cast — safe conversions known at compile time
double d = 3.7;
int i = static_cast<int>(d); // truncates to 3
Base* b = static_cast<Base*>(derived_ptr); // upcast
// dynamic_cast — safe downcast with RTTI (returns nullptr on failure)
Base* base_ptr = new Derived();
if (Derived* dp = dynamic_cast<Derived*>(base_ptr)) {
dp->derived_method(); // safe to call
}
// const_cast — add or remove const (UB if object was originally const)
const int cx = 10;
int& ref = const_cast<int&>(cx); // dangerous — only for const-incorrect APIs
// reinterpret_cast — bitwise reinterpretation, implementation-defined
uint64_t bits = reinterpret_cast<uint64_t>(some_ptr);
// Common in type-punning: read double bits as int64
double val = 1.0;
int64_t raw;
std::memcpy(&raw, &val, sizeof(val)); // safer than reinterpret_cast for type-punning
Attributes¶
C++17 standard attributes that communicate intent to the compiler and tools.
// [[nodiscard]] — warn if return value is ignored
[[nodiscard]] int compute() { return 42; }
// compute(); // ← compiler warning
// [[nodiscard]] on class — every function returning it warns on discard
struct [[nodiscard]] ErrorCode { int code; };
// [[likely]] / [[unlikely]] (C++20) — branch prediction hints
if ([[likely]] x > 0) { /* hot path */ }
if ([[unlikely]] x == 0) { /* cold path */ }
// [[maybe_unused]] — suppress unused variable warnings
[[maybe_unused]] int debug_val = compute_debug_info();
// [[fallthrough]] — silent intentional switch fallthrough
switch (n) {
case 1:
do_one();
[[fallthrough]];
case 2:
do_two(); // runs for n==1 and n==2
break;
}
// [[deprecated]] — marks API as deprecated
[[deprecated("use new_func() instead")]]
void old_func() {}
Quick Reference Table¶
| Feature | Syntax | Notes |
|---|---|---|
auto | auto x = expr; | Type deduced from initializer |
decltype | decltype(expr) x; | Exact type of expr (reference-preserving) |
| Rvalue ref | T&& x = std::move(y); | Enables move semantics |
| Perfect forward | std::forward<T>(arg) | Use inside T&& templates only |
| Structured binding | auto [a,b] = pair; | Works on pairs, tuples, structs, arrays |
constexpr | constexpr T f(...) | Evaluated at compile time when possible |
if constexpr | if constexpr (cond) | Dead branch not instantiated |
| Fold expr | (args + ...) | Reduces variadic pack with operator |
| Lambda | [cap](p) spec { } | Captures: = copy, & ref |
__builtin_popcount | __builtin_popcount(x) | Count set bits (GCC only) |
__int128 | __int128 x = val; | 128-bit integer, no I/O support |
static_cast | static_cast<T>(x) | Safe compile-time conversion |
dynamic_cast | dynamic_cast<T*>(p) | Safe downcast, returns nullptr on fail |
[[nodiscard]] | [[nodiscard]] T f() | Warn on ignored return value |
<=> | auto op<=>(T) = default | Generates all comparison operators |