C++23 è finalizzato. Ecco che arriva C++26

Feb 21 2023
Dal nostro post precedente sei mesi fa, si sono svolte due riunioni del gruppo di lavoro internazionale sulla standardizzazione del C++. Durante il primo incontro, il comitato si è concentrato sul perfezionamento delle caratteristiche del C++23, che includono: operatore statico[] L'estate scorsa, il comitato ha aggiunto l'operatore statico() al C++23 e ha reso possibile definire l'operatore[] per più argomentazioni.

Dal nostro post precedente sei mesi fa, si sono svolte due riunioni del gruppo di lavoro internazionale sulla standardizzazione del C++.

Durante il primo incontro, il comitato si è concentrato sul perfezionamento delle caratteristiche del C++23, che includono:

  • static operator[]
  • static constexprnelle constexprfunzioni
  • Basato sull'intervallo sicuro per
  • Interazione std::printcon altri output della console
  • Interfaccia monadica perstd::expected
  • static_assert(false)e altre caratteristiche
  • std::gete std::tuple_sizeper aggregati
  • #embed
  • Ottenere std::stacktracedalle eccezioni
  • Coroutine impilabili

operatore statico[]

L'estate scorsa, il comitato ha aggiunto the static operator()a C++23 e ha reso possibile definire the operator[]per più argomenti. Il passaggio logico successivo è stato quello di offrire a questi operatori pari opportunità, vale a dire aggiungere la possibilità di scrivere l'estensione static operator[].

enum class Color { red, green, blue };

struct kEnumToStringViewBimap {
  static constexpr std::string_view operator[](Color color) noexcept {
    switch(color) {
    case Color::red: return "red";
    case Color::green: return "green";
    case Color::blue: return "blue";
    }
  }

  static constexpr Color operator[](std::string_view color) noexcept {
    if (color == "red") {
      return Color::red;
    } else if (color == "green") {
      return Color::green;
    } else if (color == "blue") {
      return Color::blue;
    }
  }
};

// ...
assert(kEnumToStringViewBimap{}["red"] == Color::red);

Potrebbe essere una sorpresa, ma il codice è in realtà molto efficiente. Gli sviluppatori di compilatori utilizzano approcci simili e abbiamo implementato una tecnica simile anche nel framework userver . Abbiamo creato una classe separata chiamata utils::TrivialBiMap con un'interfaccia più comoda.

constexpr utils::TrivialBiMap kEnumToStringViewBimap = [](auto selector) {
  return selector()
      .Case("red", Color::red)
      .Case("green", Color::green)
      .Case("blue", Color::blue);
};

constexpr statico nelle funzioni constexpr

C++23 ha ampliato le sue funzionalità con l'aggiunta di constexpr to_chars/from_chars. Tuttavia, alcuni implementatori hanno riscontrato un problema. Diverse librerie standard contenevano matrici costanti per una rapida conversione di string<>number, che erano dichiarate come variabili statiche all'interno delle funzioni. Sfortunatamente, questo ne ha impedito l'uso all'interno constexprdelle funzioni. Questo problema può essere risolto, ma le soluzioni alternative sembravano davvero goffe.

Alla fine, il comitato ha risolto il problema consentendo l'uso di static constexprvariabili all'interno constexprdelle funzioni, come indicato in P2647R1 . Un piccolo, ma gradito miglioramento.

Basato sull'intervallo sicuro per

Questa è probabilmente la notizia più entusiasmante emersa dagli ultimi due incontri!

A tal proposito, iniziamo con un divertente indovinello: riesci a identificare il bug nel codice?

class SomeData {
 public:
  // ...
  const std::vector<int>& Get() const { return data_; }
 private:
  std::vector<int> data_;
};

SomeData Foo();

int main() {
  for (int v: Foo().Get()) {
    std::cout << v << ',';
  }
}

I cicli for basati su intervallo coinvolgono molti processi sottostanti e, di conseguenza, questi tipi di bug potrebbero non essere sempre evidenti. Sebbene sia possibile rilevare efficacemente questi problemi attraverso test con disinfettanti, i progetti moderni generalmente li includono come pratica standard (in Yandex, non facciamo eccezione a questa regola). Tuttavia, l'ideale sarebbe evitare del tutto tali bug quando possibile.

A RG21, abbiamo fatto il nostro primo tentativo di migliorare questa situazione quattro anni fa con D0890R0 . Purtroppo, il processo si è bloccato durante la fase di discussione.

Per fortuna, Nicolai Josuttis ha raccolto l'iniziativa e in C++ 23 un codice simile non creerà più un riferimento penzolante. Tutti gli oggetti creati a destra di :un ciclo for basato su intervallo ora vengono distrutti solo quando si esce dal ciclo.

Per maggiori dettagli tecnici fare riferimento al documento P2718R0 .

std::stampa

In C++23, c'è un piccolo ma notevole aggiornamento a std::print: il suo output è stato adattato per essere "sincronizzato" con altri output di dati. Anche se è improbabile che le librerie standard sui sistemi operativi moderni subiscano cambiamenti evidenti, lo standard aggiornato ora garantisce che i messaggi vengano inviati alla console nell'ordine in cui appaiono nel codice sorgente:

printf("first");
std::print("second");

Una caratteristica abbastanza significativa è stata aggiunta al C++23 all'ultimo momento: è stata inclusa un'interfaccia monadica per std::expected, simile all'interfaccia monadica già disponibile per std::optional.

using std::chrono::system_clock;
std::expected<system_clock, std::string> from_iso_str(std::string_view time);
std::expected<formats::bson::Timestamp, std::string> to_bson(system_clock time);
std::expected<int, std::string> insert_into_db(formats::bson::Timestamp time);

// Somewhere in the application code...
from_iso_str(input_data)
    .and_then(&to_bson)
    .and_then(&insert_into_db)
    // Throws “Exception” if any of the previous steps resulted in an error
    .transform_error([](std::string_view error) -> std::string_view {
        throw Exception(error);
    })
;

static_assert(false) e altro

Oltre alle significative modifiche sopra delineate, è stato apportato un vasto numero di revisioni per eliminare gli spigoli minori e migliorare lo sviluppo quotidiano. Ad esempio, i formattatori per std::thread::ide std::stacktrace( P2693 ) in modo che possano essere utilizzati con std::printe std::format. std::start_lifetime_asha anche ricevuto ulteriori controlli in fase di compilazione in P2679 . In particolare, static_assert(false)nelle funzioni modello non si attivano più senza istanziare la funzione, il che significa che il codice come il seguente compilerà e rilascerà la diagnostica solo se viene passato il tipo di dati errato:

template <class T>
int foo() {
    if constexpr (std::is_same_v<T, int>) {
      return 42;
    } else if constexpr (std::is_same_v<T, float>) {
      return 24;
    } else {
      static_assert(false, "T should be an int or a float");
    }
}

#include <ranges>

constexpr std::string_view days[] = {
    "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
};

for(const auto & [index, value]: std::views::enumerate(days)) {
    print("{} {} \n", index, value);
}

std::get e std::tuple_size per gli aggregati

C'è una nuova entusiasmante idea per migliorare il C++ che stiamo già utilizzando attivamente in Yandex Go e nel framework userver, ed è disponibile per chiunque lo desideri grazie a Boost.PFR .

Se stai scrivendo una libreria di modelli generici, è probabile che dovrai usare std::tuplee std::pair. Tuttavia, ci sono alcuni problemi con questi tipi. In primo luogo, rendono il codice difficile da leggere e comprendere poiché i campi non hanno nomi chiari e può essere difficile discernere il significato di qualcosa come std::get<0>(tuple). Inoltre, gli utenti della tua libreria potrebbero non voler lavorare direttamente con questi tipi e creeranno oggetti di questi tipi subito prima di chiamare i tuoi metodi, il che può essere inefficiente a causa della copia dei dati. In secondo luogo, std::tuplee std::pairnon "propagare" la banalità dei tipi che memorizzano. Di conseguenza, durante il passaggio e la restituzione std::tuplee std::pairdalle funzioni, il compilatore potrebbe generare codice meno efficiente.

Tuttavia, gli aggregati - strutture con campi pubblici e senza funzioni speciali - sono esenti dagli inconvenienti sopra menzionati.

L'idea alla base di P2141R0 è di consentire l'uso di aggregati in codice generico creandoli std::gete std::tuple_sizelavorando con essi. Ciò consentirebbe agli utenti di passare le loro strutture direttamente alla libreria generica senza copiarle inutilmente.

L'idea è stata ben accolta dal comitato e lavoreremo per testare e affrontare eventuali asperità in futuro.

#incorporare

Attualmente, c'è uno sviluppo attivo su un nuovo standard del linguaggio C (quello senza classi, senza ++), che include molte funzionalità utili che esistono da tempo in C++ (come , , , , , ), nullptroltre autoa constexprfunzionalità static_assertcompletamente thread_localnuove [[noreturn]]per C++. La grande notizia è che alcune nuove funzionalità verranno trasferite dal nuovo standard C a C++26.

Una di queste novità è #embed— una direttiva del preprocessore per sostituire il contenuto di un file come un array in fase di compilazione:

const std::byte icon_display_data[] = {
    #embed "art.png"
};

Ottenere std::stacktrace dalle eccezioni

L'idea del P2370 di WG21 ha incontrato una battuta d'arresto inaspettata.

La possibilità di ottenere una traccia dello stack da un'eccezione è presente nella maggior parte dei linguaggi di programmazione. Questa funzione è incredibilmente utile e consente una diagnostica più informativa e comprensibile piuttosto che messaggi di errore non informativi come Caught exception: map::at:

Caught exception: map::at, trace:
0# get_data_from_config(std::string_view) at /home/axolm/basic.cpp:600
1# bar(std::string_view) at /home/axolm/basic.cpp:6
2# main at /home/axolm/basic.cpp:17

Sfortunatamente, il comitato internazionale non ha abbracciato completamente questa idea. Indagheremo sulle preoccupazioni e lavoreremo per perfezionare l'idea nella speranza di ottenere più sostegno.

Coroutine impilabili

Dopo anni di lavoro, lo standard C++ è finalmente vicino all'aggiunta del supporto di base per le coroutine stackful in C++26 (vedere P0876 ). Vale la pena approfondire le coroutine stackful vs. stackless.

Le coroutine senza stack richiedono il supporto del compilatore e non possono essere implementate da sole come libreria. Le coroutine stackful, d'altra parte, possono essere implementate da sole, ad esempio con Boost.Context .

I primi offrono un'allocazione della memoria più efficiente, un'ottimizzazione del compilatore potenzialmente migliore e la capacità di distruggerli rapidamente. Sono anche già disponibili in C++20.

Questi ultimi sono molto più facili da integrare nei progetti esistenti, poiché non richiedono una riscrittura completa in un nuovo linguaggio come fanno le coroutine senza stack. In effetti, nascondono completamente i dettagli di implementazione all'utente, consentendo loro di scrivere codice lineare semplice che è asincrono sotto il cofano.

Impilabile :

auto data = co_await socket.receive();
process(data);
co_await socket.send(data);
co_return; // requires function to return a special data type

auto data = socket.receive();
process(data);
socket.send(data);

thread_local int i = 0;
// ...
++i;
foo();  // Stackful coroutines can switch execution threads
assert(i > 0);  // The compiler saved the address in a register; we’re working with the TLS of another thread

E così è fatto! C++23 è stato ufficialmente inviato alle autorità ISO superiori e sarà presto pubblicato come standard completo.

Nel frattempo, lo sviluppo di C++26 è in pieno svolgimento e ci sono interessanti prospettive per esecutori, networking, pattern matching e riflessione statica. Se hai idee innovative per migliorare il C++, sentiti libero di condividerle. O, ancora meglio, considera di presentare una proposta. Saremo felici di aiutarti!