Anteprima di C++23 e C++26
Esplora std::expected, std::print, deducing this e i miglioramenti ai ranges di C++23, e dai un'occhiata alle funzionalità in arrivo con C++26 come contract, reflection e pattern matching.
L'Evoluzione Continua
Il C++23 è una release di "completamento" che colma le lacune lasciate dal C++20 e aggiunge funzionalità molto richieste. std::expected porta una gestione degli errori in stile Rust, std::print offre un output moderno, deducing this abilita potenti pattern di metaprogrammazione e i ranges guadagnano decine di nuove view. Nel frattempo, il C++26 si sta delineando come un'altra release maggiore con contract, reflection e pattern matching — funzionalità che cambieranno radicalmente il modo in cui scriviamo codice C++ sicuro ed espressivo.
std::expected: Gestione Moderna degli Errori
std::expected contiene o un valore di successo di tipo T o un errore di tipo E. Unito alle operazioni monadiche (.and_then(), .transform(), .or_else()), permette una gestione degli errori componibile senza eccezioni:
#include <iostream>
#include <expected>
#include <string>
#include <charconv>
#include <system_error>
enum class ParseError {
empty_input,
invalid_format,
out_of_range
};
std::expected<int, ParseError> parse_int(std::string_view sv) {
if (sv.empty()) return std::unexpected(ParseError::empty_input);
int result{};
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), result);
if (ec == std::errc::invalid_argument)
return std::unexpected(ParseError::invalid_format);
if (ec == std::errc::result_out_of_range)
return std::unexpected(ParseError::out_of_range);
if (ptr != sv.data() + sv.size())
return std::unexpected(ParseError::invalid_format);
return result;
}
std::expected<int, ParseError> double_if_positive(int val) {
if (val <= 0) return std::unexpected(ParseError::out_of_range);
return val * 2;
}
int main() {
// Direct usage
auto result = parse_int("42");
if (result) {
std::cout << "Parsed: " << *result << "\n";
} else {
std::cout << "Error code: " << static_cast<int>(result.error()) << "\n";
}
// Monadic chaining — like Rust's Result::and_then
auto chained = parse_int("21")
.and_then(double_if_positive) // int -> expected<int, E>
.transform([](int v) { return v + 1; }) // int -> int (wrapped)
.or_else([](ParseError e) -> std::expected<int, ParseError> {
std::cout << "Recovering from error\n";
return 0; // default value on error
});
std::cout << "Chained result: " << *chained << "\n"; // 43
return 0;
}std::print e std::println
std::print e std::println uniscono std::format all'output, sostituendo std::cout << nella maggior parte dei casi d'uso. Sono più veloci, type-safe e producono codice leggibile:
#include <print>
#include <vector>
#include <string>
#include <ranges>
int main() {
// Basic printing — no more << chains!
std::println("Hello, {}!", "C++23");
std::print("No newline here. ");
std::println("But here.");
// Formatting works just like std::format
int x = 42;
double pi = 3.14159;
std::println("x = {}, pi = {:.2f}", x, pi);
std::println("Hex: {:#x}, Binary: {:#b}", 255, 255);
// Print to stderr
std::println(stderr, "Warning: something happened");
// Containers and ranges (C++23 formatter support)
std::vector<int> v{1, 2, 3, 4, 5};
std::println("Vector: {}", v); // [1, 2, 3, 4, 5]
// Formatted table
std::println("{:<15} {:>10} {:>10}", "Name", "Score", "Grade");
std::println("{:<15} {:>10} {:>10}", "Alice", 95, "A");
std::println("{:<15} {:>10} {:>10}", "Bob", 87, "B+");
return 0;
}Miglioramenti ai Ranges: zip, chunk, slide
Il C++23 aggiunge potenti nuove view per i ranges che coprono pattern comuni i quali prima richiedevano cicli scritti a mano o librerie di terze parti:
#include <print>
#include <ranges>
#include <vector>
#include <string>
int main() {
std::vector<std::string> names{"Alice", "Bob", "Carol"};
std::vector<int> scores{95, 87, 92};
// zip — combine multiple ranges element-wise
for (auto [name, score] : std::views::zip(names, scores)) {
std::println("{}: {}", name, score);
}
std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
// chunk — split into groups of N
for (auto chunk : data | std::views::chunk(3)) {
std::print("[ ");
for (int v : chunk) std::print("{} ", v);
std::println("]");
}
// [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ]
// slide — sliding window of size N
for (auto window : data | std::views::slide(3)) {
std::print("( ");
for (int v : window) std::print("{} ", v);
std::println(")");
}
// ( 1 2 3 ) ( 2 3 4 ) ( 3 4 5 ) ... ( 7 8 9 )
// cartesian_product — all combinations
std::vector<char> suits{'H', 'D', 'C', 'S'};
std::vector<int> ranks{1, 2, 3};
for (auto [suit, rank] : std::views::cartesian_product(suits, ranks)) {
std::print("{}{} ", suit, rank);
}
std::println("");
// enumerate — index + value (finally!)
for (auto [i, name] : std::views::enumerate(names)) {
std::println("[{}] {}", i, name);
}
return 0;
}Deducing this
Il deducing this del C++23 (chiamato anche "parametro oggetto esplicito") permette alle funzioni membro di ricevere this come parametro esplicito e dedotto. Questo elimina la necessità di duplicare gli overload const/non-const, abilita le lambda ricorsive e semplifica il CRTP:
struct Widget {
std::string name;
// Prima del C++23: due overload identici
// const std::string& get_name() const { return name; }
// std::string& get_name() { return name; }
// C++23: una sola funzione gestisce entrambi i casi
template<typename Self>
auto&& get_name(this Self&& self) {
return std::forward<Self>(self).name;
}
};Il parametro this Self&& self deduce se l'oggetto è const, non-const, lvalue o rvalue — e lo inoltra correttamente. Questo pattern, chiamato idioma "deducing this", può dimezzare gli insiemi di overload delle funzioni membro.
Anteprima C++26: Contract, Reflection, Pattern Matching
Ci si aspetta che il C++26 includa diverse funzionalità rivoluzionarie:
Contract — pre, post e contract_assert permettono di esprimere precondizioni, postcondizioni e asserzioni che il sistema di build può abilitare o disabilitare:
int sqrt_int(int x)
pre(x >= 0)
post(r: r * r <= x)
{
// implementazione
}Reflection statica — Ispeziona tipi, membri ed enumeratori a tempo di compilazione. Genera codice a partire dai campi di una struct, deriva automaticamente la serializzazione e molto altro — tutto senza macro.
Pattern Matching — Le espressioni inspect permettono di fare matching su tipi, valori e struttura:
inspect (variant_value) {
<int> i => std::println("int: {}", i);
<string> s => std::println("string: {}", s);
__ => std::println("other");
};Sender/Receiver (std::execution) — Un framework standard per l'esecuzione asincrona che offre concorrenza strutturata, sostituendo le implementazioni artigianali dei thread pool.
Come restare al passo con l'evoluzione del C++:
1. Fissa uno standard minimo per il tuo progetto (oggi il C++17 è una base solida per la maggior parte del codice di produzione)
2. Usa i flag del compilatore per selezionare lo standard: -std=c++17, -std=c++20, -std=c++23
3. Controlla il supporto dei compilatori su cppreference.com/compiler_support — non tutti i compilatori implementano ogni funzionalità
4. Usa le feature test macro come __cpp_concepts e __cpp_lib_expected per usare condizionalmente le funzionalità più recenti
5. Leggi le proposte (wg21.link/pXXXX) delle funzionalità che intendi adottare
6. Non rincorrere ogni novità — adotta ciò che rende il tuo codice più semplice e più corretto, non solo più moderno
Adottare troppo presto le funzionalità dello standard più recente ha costi concreti:
Bug dei compilatori: le nuove funzionalità hanno spesso bug di implementazione che emergono solo con l'uso in produzione. I moduli e le coroutine del C++20, per esempio, hanno avuto bug significativi nei compilatori per 2-3 anni dopo la standardizzazione.
Tooling incompleto: debugger, profiler e analizzatori statici potrebbero non supportare del tutto le funzionalità più recenti. Il supporto degli IDE (IntelliSense, completamento del codice) è spesso in ritardo.
Portabilità: se il tuo codice deve compilare su più piattaforme (Linux, macOS, Windows, embedded), sei limitato all'intersezione di ciò che tutti i compilatori di destinazione supportano.
Preparazione del team: le nuove funzionalità richiedono che tutto il team le impari. Una funzionalità compresa da un solo sviluppatore crea un collo di bottiglia per la manutenzione.
std::expectedabilita una gestione degli errori in stile Rust con concatenamento monadico (.and_then(),.transform())std::print/std::printlnsostituisconostd::cout <<con un output formattato pulito, veloce e type-safe- I ranges del C++23 aggiungono le view
zip,chunk,slide,cartesian_productedenumerate - Deducing this elimina la duplicazione degli overload const/non-const delle funzioni membro
- Il C++26 punta a contract, reflection statica, pattern matching e
std::executionper l'asincronia - Adotta i nuovi standard in modo graduale — verifica il supporto dei compilatori, testa a fondo e assicurati che il team sia pronto
Quiz — Metti alla prova le tue conoscenze
(15 XP)1. Che cosa rappresenta `std::expected<int, Error>`?
2. Che cosa produce `std::views::zip(names, scores)`?
3. Quale funzionalità del C++26 permette di specificare precondizioni e postcondizioni delle funzioni?