Storia degli Standard C++

C++98/03

Primo standard. STL, template base, eccezioni, RTTI.

C++11 πŸš€

Rivoluzione. auto, lambda, move semantics, smart pointers, thread.

C++14

Raffinamento. Lambda generiche, make_unique, binary literals.

C++17 ⭐

Structured bindings, if constexpr, std::optional, std::variant, filesystem.

C++20

Concepts, Ranges, Coroutines, modules, std::format.

C++23

std::expected, std::print, more ranges.

Compilazione Per usare C++17: g++ -std=c++17 -Wall file.cpp

1. auto e Deduzione dei Tipi (C++11)

C++auto, decltype, trailing return types
#include <iostream>
#include <vector>
#include <map>
#include <typeinfo>
using namespace std;

int main() {
    // auto deduce il tipo dal valore iniziale
    auto x = 42;          // int
    auto y = 3.14;        // double
    auto z = 3.14f;       // float
    auto s = "ciao"s;     // std::string (con suffisso s, C++14)
    auto b = true;        // bool

    cout << typeid(x).name() << "\n"; // i (int)

    // Molto utile per tipi complessi
    map<string, vector<int>> mappa;
    mappa["a"] = {1, 2, 3};

    // Senza auto:
    // map<string, vector<int>>::iterator it = mappa.begin();
    // Con auto:
    auto it = mappa.begin();

    // decltype: deduce il tipo di un'espressione
    int a=5, b2=10;
    decltype(a + b2) risultato = a + b2;  // int
    cout << risultato << "\n";

    // Range-based for loop (C++11) β€” usa auto
    vector<int> v = {1, 2, 3, 4, 5};

    for (auto elem : v) cout << elem << " ";  // Copia
    cout << "\n";

    for (auto &elem : v) elem *= 2;            // Modifica (reference)
    for (const auto &elem : v) cout << elem << " ";  // Sola lettura
    cout << "\n";

    // auto con const e reference
    const auto &ci = v[0];  // const int&
    auto *ptr = &a;          // int*

    return 0;
}

2. Lambda Expressions (C++11)

C++Lambda β€” sintassi, capture, usi pratici
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

int main() {
    // Sintassi: [capture](params) -> return_type { body }

    // Lambda semplice
    auto saluta = [](const string &nome) {
        cout << "Ciao " << nome << "!\n";
    };
    saluta("Mario");

    // Lambda con valore di ritorno esplicito
    auto somma = [](int a, int b) -> int { return a + b; };
    cout << somma(3, 4) << "\n";

    // Capture by value [=]
    int base = 10;
    auto aggiungi_base = [base](int x) { return x + base; };
    // base viene copiato al momento della creazione della lambda

    // Capture by reference [&]
    int contatore = 0;
    auto incrementa = [&contatore]() { contatore++; };
    incrementa(); incrementa(); incrementa();
    cout << "Contatore: " << contatore << "\n";  // 3

    // Capture tutto: [=] copia tutto, [&] reference a tutto
    int a=1, b2=2;
    auto tutto_per_valore = [=]() { return a + b2; };
    auto tutto_per_ref    = [&]() { a++; b2++; };

    // Lambda mutabile (puΓ² modificare le copie)
    int n = 5;
    auto inc = [n]() mutable { n++; return n; };
    cout << inc() << "\n";  // 6 (ma n esterno rimane 5)
    cout << n << "\n";      // 5

    // Uso con algoritmi STL β€” il caso d'uso piΓΉ comune
    vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};

    // Sort personalizzato
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // Decrescente

    // Count_if
    int pari = count_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
    cout << "Numeri pari: " << pari << "\n";

    // Transform
    vector<int> quadrati(v.size());
    transform(v.begin(), v.end(), quadrati.begin(), [](int x) { return x*x; });

    // Lambda generica (C++14) β€” usa auto come parametro
    auto massimo = [](auto a, auto b) { return a > b ? a : b; };
    cout << massimo(3, 5) << "\n";
    cout << massimo(3.14, 2.71) << "\n";
    cout << massimo(string("abc"), string("xyz")) << "\n";

    // std::function per salvare lambda
    function<int(int)> fib = [&fib](int n) -> int {
        return n <= 1 ? n : fib(n-1) + fib(n-2);
    };
    cout << "fib(10) = " << fib(10) << "\n";

    return 0;
}

3. Move Semantics (C++11)

Ottimizzazione fondamentale: invece di copiare un oggetto temporaneo, ne prendiamo possesso delle risorse. Elimina copie inutili e rende il codice molto piΓΉ veloce.

C++Rvalue references, std::move, move constructor
#include <iostream>
#include <vector>
#include <utility>
#include <cstring>
using namespace std;

class Buffer {
    int *dati;
    size_t size;
public:
    Buffer(size_t n) : dati(new int[n]), size(n) {
        cout << "Costruttore: alloca " << n << " int\n";
    }

    // Copy constructor: O(n) β€” copia i dati
    Buffer(const Buffer &altro) : dati(new int[altro.size]), size(altro.size) {
        memcpy(dati, altro.dati, size * sizeof(int));
        cout << "Copy constructor: COPIA " << size << " int\n";
    }

    // Move constructor: O(1) β€” prende i puntatori
    Buffer(Buffer &&altro) noexcept : dati(altro.dati), size(altro.size) {
        altro.dati = nullptr;  // Invalida l'originale
        altro.size = 0;
        cout << "Move constructor: SPOSTA (nessuna copia!)\n";
    }

    // Move assignment
    Buffer &operator=(Buffer &&altro) noexcept {
        if (this != &altro) {
            delete[] dati;
            dati = altro.dati;
            size = altro.size;
            altro.dati = nullptr;
            altro.size = 0;
            cout << "Move assignment\n";
        }
        return *this;
    }

    ~Buffer() {
        if (dati) delete[] dati;
    }

    size_t getSize() const { return size; }
};

Buffer crea_buffer(size_t n) {
    Buffer buf(n);  // Costruisce
    return buf;     // Move avviene automaticamente (NRVO/RVO)
}

int main() {
    Buffer b1(100);                  // Costruttore
    Buffer b2 = b1;                  // Copy constructor
    Buffer b3 = move(b1);            // Move constructor β€” b1 Γ¨ ora invalido
    Buffer b4 = crea_buffer(50);     // Move (o RVO: ottimizzato dal compilatore)

    cout << "b2.size = " << b2.getSize() << "\n";
    cout << "b3.size = " << b3.getSize() << "\n";  // 100
    cout << "b1.size = " << b1.getSize() << "\n";  // 0 (spostato!)

    // std::move con i container
    vector<int> v1 = {1, 2, 3, 4, 5};
    vector<int> v2 = move(v1);  // O(1) β€” sposta il puntatore interno
    cout << "v1.size=" << v1.size() << " v2.size=" << v2.size() << "\n";

    return 0;
}

4. Structured Bindings e altri C++17

C++Structured bindings, if constexpr, std::optional, std::variant
#include <iostream>
#include <map>
#include <tuple>
#include <optional>
#include <variant>
#include <string>
using namespace std;

// ===== Structured Bindings (C++17) =====
tuple<int, string, double> get_info() {
    return {42, "Mario", 99.5};
}

// ===== std::optional β€” valore che potrebbe non esistere =====
optional<int> cerca_elemento(const vector<int> &v, int target) {
    for (int i=0; i<(int)v.size(); i++)
        if (v[i] == target) return i;  // Restituisce l'indice
    return nullopt;  // Non trovato
}

// ===== std::variant β€” union type-safe =====
using Numero = variant<int, double, string>;

void stampa_numero(const Numero &n) {
    if (holds_alternative<int>(n))         cout << "int: "    << get<int>(n);
    else if (holds_alternative<double>(n))  cout << "double: " << get<double>(n);
    else                                    cout << "string: " << get<string>(n);
    cout << "\n";
}

// ===== if constexpr (C++17) β€” branching a compile time =====
template<typename T>
void stampa_tipo(T val) {
    if constexpr (is_integral_v<T>)
        cout << "Intero: " << val << "\n";
    else if constexpr (is_floating_point_v<T>)
        cout << "Decimale: " << val << "\n";
    else
        cout << "Altro: " << val << "\n";
}

int main() {
    // Structured bindings
    auto [id, nome, voto] = get_info();
    cout << id << " " << nome << " " << voto << "\n";

    // Loop su map con structured bindings
    map<string, int> voti = {{"Mario",30}, {"Anna",28}, {"Luca",25}};
    for (const auto &[nome2, v] : voti) {
        cout << nome2 << ": " << v << "\n";
    }

    // std::optional
    vector<int> v = {1, 5, 3, 8, 2};
    if (auto pos = cerca_elemento(v, 8)) {
        cout << "Trovato alla posizione " << *pos << "\n";
    } else {
        cout << "Non trovato\n";
    }

    // optional con value_or
    auto r = cerca_elemento(v, 99);
    cout << "Posizione (o -1): " << r.value_or(-1) << "\n";

    // std::variant
    Numero n1 = 42;
    Numero n2 = 3.14;
    Numero n3 = string("hello");
    stampa_numero(n1);
    stampa_numero(n2);
    stampa_numero(n3);

    // if constexpr
    stampa_tipo(42);
    stampa_tipo(3.14);
    stampa_tipo(string("test"));

    return 0;
}

5. Initializer Lists e Uniform Initialization (C++11)

C++Uniform initialization, initializer_list, nullptr
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;

class Vettore {
    vector<double> dati;
public:
    // Costruttore con initializer_list
    Vettore(initializer_list<double> lista) : dati(lista) {}

    double norma() const {
        double s = 0;
        for (double d : dati) s += d*d;
        return sqrt(s);
    }

    void stampa() const {
        cout << "[";
        for (size_t i=0; i<dati.size(); i++) {
            if (i) cout << ", ";
            cout << dati[i];
        }
        cout << "]";
    }
};

struct Punto { int x, y, z; };

int main() {
    // Uniform initialization con {}
    int a{42};           // Come int a = 42
    double b{3.14};
    string s{"ciao"};
    vector<int> v{1,2,3,4,5};
    Punto p{1, 2, 3};   // Aggregate init

    // Importante: {} impedisce narrowing conversion!
    // int x{3.14};  // ERRORE di compilazione (perdi dati)
    // int x = 3.14; // OK (ma warning) β€” troncamento silenzioso

    // nullptr (C++11) β€” sostituisce NULL
    int *ptr = nullptr;       // Meglio di: int *ptr = NULL o int *ptr = 0
    if (!ptr) cout << "Puntatore nullo\n";

    // Initializer list personalizzata
    Vettore v1{1.0, 2.0, 3.0};
    v1.stampa();
    cout << " β€” norma: " << v1.norma() << "\n";

    // Range-based for con initializer_list inline
    for (int x : {10, 20, 30, 40, 50}) cout << x << " ";
    cout << "\n";

    return 0;
}

6. constexpr β€” Calcoli a Compile Time (C++11/14/17)

C++constexpr funzioni e variabili
#include <iostream>
#include <array>
using namespace std;

// constexpr: calcolata a compile time
constexpr int fattoriale(int n) {
    return n <= 1 ? 1 : n * fattoriale(n-1);
}

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n-1) + fibonacci(n-2);  // C++14 permette if in constexpr
}

// Calcola potenze di 2 a compile time
constexpr int potenza2(int n) { return n == 0 ? 1 : 2 * potenza2(n-1); }

// constexpr if (C++17)
template<typename T>
constexpr bool e_intero = is_integral_v<T>;

int main() {
    // Calcolato a compile time β€” nessuna runtime computation!
    constexpr int f10 = fattoriale(10);
    constexpr int fib15 = fibonacci(15);
    constexpr int p10 = potenza2(10);

    cout << "10! = " << f10 << "\n";        // 3628800
    cout << "fib(15) = " << fib15 << "\n"; // 610
    cout << "2^10 = " << p10 << "\n";       // 1024

    // Usabile come dimensione di array a compile time
    array<int, fattoriale(5)> arr;  // array di 120 elementi
    cout << "Array size: " << arr.size() << "\n";

    // constexpr vs #define
    // #define MAX 100        // Nessun tipo, problematico
    constexpr int MAX = 100;  // Ha tipo, debuggabile, namespace-safe

    return 0;
}

7. Highlights di C++20

C++Concepts, Ranges, std::format (C++20)
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include <format>   // C++20
#include <concepts> // C++20
using namespace std;

// Concepts: vincoli sui template (C++20)
template<typename T>
concept Numerico = is_arithmetic_v<T>;

template<Numerico T>
T somma(T a, T b) { return a + b; }

// Range-based programming (C++20)
void demo_ranges() {
    vector<int> v = {5,3,8,1,9,2,7,4,6};

    // Filtra e trasforma in modo dichiarativo
    auto risultato = v
        | views::filter([](int x){ return x % 2 == 0; })   // Pari
        | views::transform([](int x){ return x * x; });    // Elevati al quadrato

    for (int x : risultato) cout << x << " ";
    cout << "\n";

    // Prendi i primi 3
    auto primi3 = v | views::take(3);
    for (int x : primi3) cout << x << " ";
    cout << "\n";
}

// std::format (C++20) β€” come Python f-string
void demo_format() {
    string nome = "Mario";
    int eta = 30;
    double pi = 3.14159;

    // Simile a printf ma type-safe
    cout << format("Nome: {}, EtΓ : {}\n", nome, eta);
    cout << format("Pi: {:.4f}\n", pi);
    cout << format("Hex: {:#x}\n", 255);
    cout << format("Larghezza: {:>10}\n", "destra");
}

int main() {
    // Concepts
    cout << somma(3, 4) << "\n";        // OK: int
    cout << somma(1.5, 2.7) << "\n";    // OK: double
    // somma(string("a"), string("b")); // ERRORE: non soddisfa Numerico

    // Ranges
    demo_ranges();

    // Format (richiedono compilatore recente)
    // demo_format();

    return 0;
}
πŸ‹οΈ Esercizi
Metti in pratica il C++ moderno.
1
Refactoring con auto e lambda
FacileC++11

Riscrivi questo codice C++03 usando le feature moderne: usa auto per i tipi, range-based for loop, lambda invece dei funtori, e nullptr invece di NULL.
Input: vettore di interi, trova i pari, ordinali, stampali.

C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {7,2,9,4,1,8,3,6,5};

    // Filtra pari con remove_if + lambda
    vector<int> pari;
    for (auto x : v)
        if (x % 2 == 0) pari.push_back(x);

    // Ordina con lambda
    sort(pari.begin(), pari.end(), [](int a, int b){ return a < b; });

    // Stampa
    for (const auto &x : pari) cout << x << " ";
    cout << "\n";

    // Versione ancora piΓΉ moderna con algoritmi STL
    copy_if(v.begin(), v.end(), back_inserter(pari), [](int x){ return x%2==0; });
    sort(pari.begin(), pari.end());
    for_each(pari.begin(), pari.end(), [](int x){ cout << x << " "; });
    return 0;
}
2
Classe con Move Semantics
MedioMove semantics

Implementa una classe Matrix che gestisce una matrice dinamica. Implementa il Rule of Five: distruttore, copy constructor, copy assignment, move constructor, move assignment. Verifica che spostare una matrice sia O(1) mentre copiarla Γ¨ O(nΒ²).

C++
#include <iostream>
#include <cstring>
#include <utility>
using namespace std;

class Matrix {
    double *dati;
    int righe, colonne;
public:
    Matrix(int r, int c) : righe(r), colonne(c) {
        dati = new double[r*c]();
        cout << "Alloca " << r*c << " elementi\n";
    }
    double &at(int r, int c) { return dati[r*colonne+c]; }

    // 1. Distruttore
    ~Matrix() { delete[] dati; }

    // 2. Copy constructor β€” O(n*m)
    Matrix(const Matrix &m) : righe(m.righe), colonne(m.colonne) {
        dati = new double[righe*colonne];
        memcpy(dati, m.dati, righe*colonne*sizeof(double));
        cout << "Copiata matrice " << righe << "x" << colonne << "\n";
    }

    // 3. Copy assignment
    Matrix &operator=(const Matrix &m) {
        if (this != &m) {
            delete[] dati;
            righe=m.righe; colonne=m.colonne;
            dati = new double[righe*colonne];
            memcpy(dati, m.dati, righe*colonne*sizeof(double));
        }
        return *this;
    }

    // 4. Move constructor β€” O(1)
    Matrix(Matrix &&m) noexcept : dati(m.dati), righe(m.righe), colonne(m.colonne) {
        m.dati=nullptr; m.righe=m.colonne=0;
        cout << "Spostata matrice (O(1))\n";
    }

    // 5. Move assignment
    Matrix &operator=(Matrix &&m) noexcept {
        if(this!=&m){delete[]dati;dati=m.dati;righe=m.righe;colonne=m.colonne;m.dati=nullptr;}
        return *this;
    }

    int size() const { return righe*colonne; }
};

int main() {
    Matrix a(100, 100);   // 10000 elementi
    Matrix b = a;          // COPIA β€” costosa
    Matrix c = move(a);    // SPOSTA β€” O(1)
    cout << "c.size=" << c.size() << " a.size=" << a.size() << "\n";
    return 0;
}
3
Pipeline funzionale con lambda
DifficileFunctional / C++17

Crea una classe Pipeline che permette di costruire una catena di trasformazioni su un vettore usando filter, map, reduce e take con una sintassi fluente: Pipeline(v).filter(...).map(...).take(5).result().

C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
using namespace std;

class Pipeline {
    vector<int> dati;
public:
    Pipeline(vector<int> v) : dati(move(v)) {}

    Pipeline filter(function<bool(int)> pred) {
        vector<int> res;
        copy_if(dati.begin(), dati.end(), back_inserter(res), pred);
        return Pipeline(move(res));
    }

    Pipeline map(function<int(int)> f) {
        vector<int> res(dati.size());
        transform(dati.begin(), dati.end(), res.begin(), f);
        return Pipeline(move(res));
    }

    Pipeline take(int n) {
        vector<int> res(dati.begin(), dati.begin() + min(n, (int)dati.size()));
        return Pipeline(move(res));
    }

    Pipeline sort_asc() {
        auto d = dati; sort(d.begin(), d.end()); return Pipeline(move(d));
    }

    int reduce(function<int(int,int)> f, int init=0) {
        return accumulate(dati.begin(), dati.end(), init, f);
    }

    vector<int> result() const { return dati; }

    void print() const {
        for (int x : dati) cout << x << " "; cout << "\n";
    }
};

int main() {
    vector<int> v = {5,3,8,1,9,2,7,4,6,10,11,12};

    Pipeline(v)
        .filter([](int x){ return x%2==0; })   // Solo pari
        .map([](int x){ return x*x; })           // Eleva al quadrato
        .sort_asc()                               // Ordina
        .take(4)                                  // Prendi i primi 4
        .print();
    // Output: 4 16 36 64 100 144 β†’ prende i 4 piΓΉ piccoli

    int somma = Pipeline(v)
        .filter([](int x){ return x>5; })
        .reduce([](int acc, int x){ return acc+x; });
    cout << "Somma elementi > 5: " << somma << "\n";
    return 0;
}