20 Funzionalità C++20 più piccole ma utili

20 Funzionalità C++20 più piccole ma utili

C++ 20 è enorme e pieno di molte funzionalità di grandi dimensioni. Solo per citarne alcuni:moduli, coroutine, concetti, intervalli, calendario e fuso orario, libreria di formattazione.

Ma, come sai, non è tutto.

A seconda di come contiamo, C++20 ha portato circa 80 funzionalità della libreria e 70 modifiche alla lingua, quindi c'è molto da coprire :)

In questo articolo, ti mostrerò 20 cose C++20 più piccole che sono molto utili e buoni a sapersi. Dieci elementi del linguaggio e altri dieci per la libreria standard. La maggior parte di loro con un esempio interessante.

Entriamo subito nel testo!

Documenti e fonti

Puoi trovare l'intera bozza di C++20 qui:

  • https://timsong-cpp.github.io/cppwp/n4861/ (dopo Praga, bozza di marzo 2020)

Ed ecco un'ottima pagina di riepilogo con il supporto del compilatore in C++ Reference:

  • https://en.cppreference.com/w/cpp/compiler_support#cpp20

Ecco anche un altro confronto delle modifiche tra C++17 e C++20:

  • P2131 di Thomas Köppe

Funzioni linguistiche

Iniziamo con i miglioramenti che interessano la lingua.

1. Modelli di funzione abbreviati e Auto vincolata

Grazie alla concisa sintassi concettuale, puoi anche scrivere modelli senza il template<typename...> parte.

Con auto non vincolato :

void myTemplateFunc(auto param) { }

Il codice è equivalente al seguente stile modello "normale":

template <typename T>
void myTemplateFunc(T param) { }

O con auto vincolato - questa volta specifichiamo un nome di concetto che il tipo deve rispettare:

template <class T>
concept SignedIntegral = std::is_signed_v<T> && std::is_integral_v<T>;

void signedIntsOnly(SignedIntegral auto val) { }

void floatsOnly(std::floating_point auto fp) { }

Vedi in @Compiler Explorer.

E poi è uguale a:

template <class T>
concept SignedIntegral = std::is_signed_v<T> && std::is_integral_v<T>;

template <SignedIntegral T>
void signedIntsOnly(T val) { }

template <std::floating_point T>
void floatsOnly(T fp) { }

Inoltre, template <SignedIntegral T> è anche una notazione a mano abbreviata per:

template <typename T>
requires SignedIntegral<T>
void signedIntsOnly(T val) { }

template <typename T>
requires std::floating_point<T>
void floatsOnly(T fp) { }

Guarda una semplice demo @Compiler Explorer.

Tale sintassi è simile a quella che potresti usare in lambda generici da C++14:

// C++14 lambda:
auto lambda = [](auto val) { };

// C++20 template function:
void myTemplateFunction(auto val) { }

Vedi il mio post sul blog separato su Concepts:C++20 Concepts - A Quick Introduction - C++ Stories.

E altro nella proposta:ancora un altro approccio per le dichiarazioni vincolate - P1141R2.

2. Sintassi del modello per Lambda generici

In C++14 abbiamo ottenuto lambda generici con auto come parametro lambda. Tuttavia, a volte non era abbastanza buono. Ecco perché in C++20 puoi anche usare la sintassi degli argomenti dei modelli "reali", anche con i concetti!

auto fn = []<typename T>(vector<T> const& vec) { 
    cout << size(vec) << “, “ << vec.capacity(); 
};

Scopri di più in Lambda Week:Going Generic - C++ Stories e nella proposta:P0428r2.

3. Miglioramenti a Constexpr

Molte piccole funzionalità e miglioramenti relativi a constexpr :

  • union - P1330
  • try e catch -P1002
  • dynamic_cast e typeid -P1327
  • allocazione constexpr P0784
  • Chiamate virtuali in espressioni costanti P1064
  • Varie constexpr bit di libreria.

Grazie a questi vari bit, abbiamo constexpr algoritmi e anche std::vector e std::string può essere utilizzato anche in fase di compilazione!

Ecco un esempio che mostra diverse funzionalità che non erano disponibili prima di C++20:

#include <numeric>

constexpr int naiveSum(unsigned int n) {
    auto p = new int[n];
    std::iota(p, p+n, 1);
    auto tmp = std::accumulate(p, p+n, 0);
    delete[] p;
    return tmp;
}

constexpr int smartSum(unsigned int n) {
    return (1+n)*(n/2);
}

int main() {
    static_assert(naiveSum(10) == smartSum(10));
    return 0;
}

Gioca a @Compiler Explorer.

Vedi di più su constexpr allocazione della memoria in un post del blog separato:constexpr Dynamic Memory Allocation, C++20 - C++ Stories

Se vuoi saperne di più su C++20 constexpr in azione, dai un'occhiata al mio articolo su The Balance Parentheses Interview Problem in C++20 constexpr - disponibile per i membri Premium di C++Stories.

4. using enum

Si tratta di una pratica funzionalità che consente di controllare la visibilità dei nomi degli enumeratori e quindi semplificarne la scrittura.

Un esempio canonico è con switch :

#include <iostream>

enum class long_enum_name { hello, world, coding };

void func(long_enum_name len) {
#if defined(__cpp_using_enum) // c++20 feature testing
    switch (len) {
        using enum long_enum_name;
        case hello: std::cout << "hello "; break;
        case world: std::cout << "world "; break;
        case coding: std::cout << "coding "; break;
    }
#else
    switch (len) {
        case long_enum_name::hello: std::cout << "hello "; break;
        case long_enum_name::world: std::cout << "world "; break;
        case long_enum_name::coding: std::cout << "coding "; break;
    }
#endif
}

enum class another_long_name { hello, breaking, code };

int main() {
    using enum long_enum_name;
    func(hello);
    func(coding);
    func(world);
    
// using enum another_long_name; // error: 'another_long_name::hello' 
                             // conflicts with a previous declaration
}

Gioca con il codice @Compiler Explorer.

Grazie a using enum NAME, puoi introdurre i nomi dell'enumeratore nell'ambito corrente.

Maggiori informazioni nella proposta P1099 o nella Dichiarazione di enumerazione - cppreference.com.

5. Tipi di classe nei parametri del modello non di tipo

Questa funzione è chiamata in breve NTTP.

Prima di C++20, per un parametro di modello non di tipo, potresti usare:

  • tipo di riferimento lvalue (a oggetto oa funzione);
  • un tipo integrale;
  • un tipo di puntatore (all'oggetto o alla funzione);
  • un puntatore al tipo di membro (all'oggetto membro o alla funzione membro);
  • un tipo di enumerazione;

Ma dal momento che C++ 20, ora possiamo aggiungere:

  • strutture e classi semplici - tipi strutturali
  • numeri in virgola mobile
  • Lambda

Un esempio di base:

#include <iostream>

template <double Ga>
double ComputeWeight(double mass) {
    return mass*Ga;
}

int main() {
    constexpr auto EarthGa = 9.81;
    constexpr auto MoonGa = 1.625;
    std::cout << ComputeWeight<EarthGa>(70.0) << '\n';
    std::cout << ComputeWeight<MoonGa>(70.0) << '\n';
}

Gioca a @Compiler Explorer

E un po' più complesso con una classe semplice:

#include <iostream>

struct Constants {
    double gravityAcceleration_ { 1.0 };

    constexpr double getGA() const { return gravityAcceleration_;}
};

template <Constants C>
double ComputeWeight(double mass) {
    return mass * C.getGA();
}

int main() {
    constexpr Constants EarthGa { 9.81 };
    constexpr Constants MoonGa { 1.625 };
    std::cout << ComputeWeight<EarthGa>(70.0) << '\n';
    std::cout << ComputeWeight<MoonGa>(70.0) << '\n';
}

Gioca a @Compiler Explorer

Nell'esempio sopra, ho usato un semplice oggetto di classe, è "un tipo strutturale". Ecco le opzioni complete per questo tipo:

  • un tipo scalare o
  • un tipo di riferimento lvalue
  • un tipo di classe letterale con le seguenti proprietà:
    • tutte le classi base e i membri dati non statici sono pubblici e non modificabili e
    • i tipi di tutte le classi base e dei membri dati non statici sono tipi strutturali o array (possibilmente multidimensionali).

Vedi tutti i requisiti in [temp.param 6]

Principali vantaggi e casi d'uso:

  • Rendi il linguaggio più coerente. Finora, C++ consentiva tipi semplici come enum, valori integrali,
  • Tipi di wrapper
  • Elaborazione delle stringhe in fase di compilazione

Maggiori informazioni nella proposta P0732R2 e flottante P1714 - virgola mobile, e la dicitura finale e chiarimenti in P1907R1

6. Inizializzatori di campi di bit predefiniti

Una piccola cosa e può essere trattata come una "correzione" per la funzionalità introdotta in C++11.

Da C++11 puoi usare l'inizializzazione dei membri di dati non statici e assegnare valori direttamente all'interno della dichiarazione di classe:

struct Type {
    int value = 100;
    int second {10001 };
};

Come sembrava, la sintassi non funzionava e non funzionava per i campi di bit. C++20 ha migliorato questa funzionalità e ora puoi scrivere:

#include <iostream>

struct Type {
    int value : 4 = 1;
    int second : 4 { 2 };
};

int main() {
    Type t;
    std::cout << t.value << '\n';
    std::cout << t.second << '\n';
}

Gioca con il codice @Compiler Explorer.

Maggiori informazioni nella proposta:P0710r1

7. Inizializzatori designati

Una caratteristica interessante che abbiamo "rubato" da C :)

In una forma base, puoi scrivere:

Type obj = { .designator = val, .designator { val2 }, ... };

Ad esempio:

struct Point { double x; double y; };
Point p { .x = 10.0, .y = 20.0 };

Designatore punta a un nome di un membro dati non statico della nostra classe, come .x o .y .

Uno dei motivi principali per utilizzare questo nuovo tipo di inizializzazione è aumentare la leggibilità.

Avere il seguente tipo:

struct Date {
    int year;
    int month;
    int day;
};

Questo è più facile da leggere:

Date inFuture { .year = 2050, .month = 4, .day = 10 };

Di:

Date inFuture { 2050, 4, 10 };

Ecco le regole principali di questa funzione:

  • Gli inizializzatori designati funzionano solo per l'inizializzazione aggregata, quindi supportano solo i tipi aggregati.
  • I designatori possono fare riferimento solo a membri dati non statici.
  • I designatori nell'espressione di inizializzazione devono avere lo stesso ordine di membri dati in una dichiarazione di classe (nel linguaggio C, possono essere in qualsiasi ordine)
  • Non tutti i membri dati devono essere specificati nell'espressione.
  • Non puoi combinare l'inizializzazione regolare con i designer.
  • Può esserci un solo designatore per un membro dati
  • Non puoi annidare i designatori.

Maggiori informazioni nella proposta:P0329r4

E ho anche un articolo separato su questo argomento:Inizializzatori designati in C++20 - Storie C++.

8. Miglioramenti agli attributi Nodiscard

[[nodiscard]] - aggiunto in C++17, è un potente attributo che potrebbe aiutare ad annotare calcoli importanti nel codice. In C++20 otteniamo diversi miglioramenti come:

  • [[nodiscard]] per costruttori - P1771
  • [[nodiscard]] con un messaggio P1301R4
  • Applica [[nodiscard]] alla libreria standard P0600R1

Ad esempio, con P1301 è possibile specificare il motivo per cui l'oggetto non deve essere eliminato. Potresti voler usare questa opzione per scrivere sull'allocazione della memoria o altre informazioni importanti che il compilatore riporterà:

[[nodiscard("Don't call this heavy function if you don't need the result!")]] bool Compute();

Inoltre, grazie a P0600 questo attributo è ora applicato in molti punti della Libreria standard, ad esempio:

  • async()
  • allocate() , operator new
  • launder() , empty()

Leggi di più nel mio post del blog separato:Applicazione dei contratti di codice con nodiscard

9. Ciclo for basato sull'intervallo con inizializzatore

Un modo utile per migliorare la sintassi per i loop basati sull'intervallo:

for (init; decl : expr)

Ad esempio:

#include <iostream>
#include <array>
#include <ranges>

void print(const std::ranges::range auto& container) {
    for (std::size_t i = 0; const auto& x : container) {
        std::cout << i << " -> " << x << '\n';
        // or std::cout << std::format("{} -> {}", i, x);
        ++i;
    }
}

int main() {
    std::array arr {5, 4, 3, 2, 1};
    print(arr);
}

Gioca con il codice @Compiler Explorer.

L'inizializzatore è anche un buon modo per acquisire oggetti temporanei:

for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK

Vedi di più nella proposta:P0614

Questo articolo è iniziato come anteprima per Patrons mesi fa. Se vuoi ottenere contenuti esclusivi, prime anteprime, materiali bonus e accesso al server Discord, iscriviti a
l'abbonamento a C++ Stories Premium.

10. Nuova parola chiave consteval - funzioni immediate

La funzionalità è meglio descritta come la citazione dalla proposta:

Vedi esempio sotto:

consteval int sum(int a, int b) {
  return a + b;
}

constexpr int sum_c(int a, int b) {
    return a + b;
}

int main() {
    constexpr auto c = sum(100, 100);
    static_assert(c == 200);

    constexpr auto val = 10;
    static_assert(sum(val, val) == 2*val);

    int a = 10;
    int b = sum_c(a, 10); // fine with constexpr function

    // int d = sum(a, 10); // error! the value of 'a' is 
                           // not usable in a constant expression
}

Vedi @Compiler Explorer.

Le funzioni immediate possono essere viste come un'alternativa alle macro in stile funzione. Potrebbero non essere visibili nel debugger (inline)

Inoltre, mentre possiamo dichiarare un constexpr variabile, non è possibile dichiarare un consteval variabile.

// consteval int some_important_constant = 42; // error

La dichiarazione di tali variabili richiedeva definizioni complicate nello Standard per casi d'uso limitati, quindi questa estensione non è stata aggiunta al linguaggio.

Maggiori informazioni nella proposta P1073

constinit

C'è anche un'altra parola chiave che è entrata in C++20 e inizia con const . È constinit . Sebbene sia un argomento più lungo, vorrei spiegare brevemente la principale differenza tra queste nuove parole chiave

In breve, constinit ci consente di dichiarare una variabile di durata di archiviazione statica che deve essere inizializzata in modo statico, ad esempio inizializzazione zero o inizializzazione costante. Ciò consente di evitare gli scenari di fiasco dell'ordine di inizializzazione statico - vedere qui:Domande frequenti su C++.

Vedi questo esempio di base:

// init at compile time
constinit int global = 42;

int main() {
    // but allow to change later...
    global = 100;
}

Gioca a @Compiler Explorer.

E vedi altro c++ - Che cos'è constinit in C++20? - Overflow dello stack.

La libreria standard

Vediamo ora alcune delle modifiche alla Libreria standard.

11. Costanti matematiche

Una nuova intestazione <numbers> con un modo moderno per ottenere la maggior parte delle costanti comuni:

namespace std::numbers {
  template<class T> inline constexpr T e_v          = /* unspecified */;
  template<class T> inline constexpr T log2e_v      = /* unspecified */;
  template<class T> inline constexpr T log10e_v     = /* unspecified */;
  template<class T> inline constexpr T pi_v         = /* unspecified */;
  template<class T> inline constexpr T inv_pi_v     = /* unspecified */;
  template<class T> inline constexpr T inv_sqrtpi_v = /* unspecified */;
  template<class T> inline constexpr T ln2_v        = /* unspecified */;
  template<class T> inline constexpr T ln10_v       = /* unspecified */;
  template<class T> inline constexpr T sqrt2_v      = /* unspecified */;
  template<class T> inline constexpr T sqrt3_v      = /* unspecified */;
  template<class T> inline constexpr T inv_sqrt3_v  = /* unspecified */;
  template<class T> inline constexpr T egamma_v     = /* unspecified */;
  template<class T> inline constexpr T phi_v        = /* unspecified */;
}

Questi numeri sono modelli di variabili, ma ci sono anche variabili constexpr inline helper come:

inline constexpr double pi = pi_v<double>;

Semplice demo:

#include <numbers>
#include <iostream>

int main() {
    std::cout << std::numbers::pi << '\n';
    using namespace std::numbers;
    std::cout << pi_v<float> << '\n';
}

Gioca a @Compiler Explorer.

Maggiori informazioni in P0631 e in Cppreference.

12. Più constexpr nella Libreria

C++20 ha migliorato le regole del linguaggio per constexpr ma poi anche la libreria standard ha preso quelle funzionalità e le ha aggiunte ai tipi di libreria. Ad esempio:

  • constexpr std::complex
  • constexpr algoritmi P0202
  • Creare std::vector constexpr -P1004
  • Creare std::string constexpr -P0980

Con tutto il supporto, possiamo scrivere il seguente codice che calcola il numero di parole in una stringa letterale, tutto in fase di compilazione:

#include <vector>
#include <string>
#include <algorithm>

constexpr std::vector<std::string> 
split(std::string_view strv, std::string_view delims = " ") {
    std::vector<std::string> output;
    size_t first = 0;

    while (first < strv.size()) {
        const auto second = strv.find_first_of(delims, first);

        if (first != second)
            output.emplace_back(strv.substr(first, second-first));

        if (second == std::string_view::npos)
            break;

        first = second + 1;
    }

    return output;
}

constexpr size_t numWords(std::string_view str) {
    const auto words = split(str);

    return words.size();
}

int main() {
    static_assert(numWords("hello world abc xyz") == 4);
}

Vedi @Compiler Explorer.

Inoltre, puoi dare un'occhiata a un altro articolo:constexpr vector e string in C++20 e One Big Limitation - C++ Stories.

Vorresti vedere di più?
Ho scritto un constexpr parser di stringhe ed è disponibile per C++ Stories Premium/Patreon membri. Scopri tutti i vantaggi Premium qui.

13. .starts_with() e .ends_with()

Infine, un modo pratico per controllare prefissi e suffissi per le stringhe in C++!

Vediamo un esempio:

#include <string>
#include <iostream>
#include <string_view>

int main(){
    const std::string url = "https://isocpp.org";
    
    // string literals
    if (url.starts_with("https") && url.ends_with(".org"))
        std::cout << "you're using the correct site!\n";
    
    if (url.starts_with('h') && url.ends_with('g'))
        std::cout << "letters matched!\n";
}

Gioca a @Wandbox.

Ho scritto un post sul blog separato su questo argomento con più esempi, quindi dai un'occhiata a:Come controllare i prefissi e i suffissi di visualizzazione di stringhe o stringhe in C++20 - Storie C++.

Alcuni altri casi d'uso (suggeriti anche dai tuoi commenti su r/programming):

  • trovare file con un certo finale (verificando il nome o l'estensione del file)
  • trovare file con un inizio specifico
  • trovare le righe in un file di testo che iniziano con una data o un prefisso
  • analisi dei formati di file di testo personalizzati

Ed ecco il link alla proposta P0457.

14. contains() funzione membro di contenitori associativi

Quando vuoi controllare se c'è un elemento all'interno di un contenitore, di solito puoi scrivere la seguente condizione:

if (container.find(key) != container.end())

Ad esempio (basato su Cppreference):

std::map<std::string, int, std::less<>> strToInt = {
        {"hello", 10},
        {"world", 100}
    };
 
for(auto& key: {"hello", "something"}) {
    if(strToInt.find(key) != strToInt.end())
        std::cout << key << ": Found\n";
    else
        std::cout << key << ": Not found\n";        
}

Ora possiamo riscrivere in:

for(auto& key: {"hello", "something"}) {
    if(strToInt.contains(key))
        std::cout << key << ": Found\n";
    else
        std::cout << key << ": Not found\n";
}

Gioca con il codice @Compiler Explorer

Come puoi vedere, il codice è più leggibile poiché l'unica funzione spiega cosa fa il codice.

L'importante è che il controllo possa essere anche "transitorio" ed "eterogeneo", ecco perché ho dichiarato il contenitore come std::map<std::string, int, std::less<>>.

Vedi la proposta:P0458R2

Nota :in C++23 abbiamo già funzioni simili per le stringhe! Vedi string.contains @Cppreference.

Puoi anche leggere ulteriori informazioni sulle utili funzioni della mappa in un articolo separato Storie di @C++:esempi di 7 funzioni utili per i contenitori associativi nel C++ moderno.

15. Cancellazione coerente del contenitore

Un pratico wrapper per il remove/erase idioma per molti contenitori nella libreria standard!

Il std::remove l'algoritmo non rimuove elementi da un determinato contenitore poiché funziona sugli iteratori di input. std::remove sposta solo gli elementi in modo che possiamo chiamare .erase() dopo. Tale tecnica sembrava essere soggetta a errori, difficile da imparare e da insegnare.

In C++20, otteniamo una serie di funzioni gratuite che hanno overload per molti contenitori e possono rimuovere elementi:

erase(container, value);
erase_if(container, predicate);

Vedi l'esempio:

#include <iostream>
#include <vector>

int main() {
    std::vector vec { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    std::erase_if(vec, [](auto& v) { return v % 2 == 0; });
    for (int i = 0; auto &v : vec) 
        std::cout << i++ << ": " << v << '\n';
}

Se esaminiamo la proposta, possiamo vedere tutte le modifiche, ad esempio:

void erase(basic_string<charT, traits, Allocator>& c, const U& value);

Equivale a:c.erase(remove(c.begin(), c.end(), value), c.end());

Ma per i contenitori associativi:

void erase_if(map<Key, T, Compare, Allocator>& c, Predicate pred);

Equivale a:

for (auto i = c.begin(), last = c.end(); i != last; ) {
    if (pred(*i))
        i = c.erase(i);
    else
        ++i;
}

Vedi di più nella proposta P1209

16. Posizione di origine

Un modo moderno per acquisire informazioni su file, funzioni o linee correnti sul codice sorgente.

Finora la tecnica comune era quella di utilizzare macro speciali:

void MyTrace(int line, const char *fileName, const char *msg, ...) { }
#define MY_TRACE(msg, ...) MyTrace(__LINE__, __FILE__, msg, __VA_ARGS__)
MYTRACE("Hello World");

Ma ora abbiamo un tipo di aiuto speciale std::source_location che è un normale oggetto C++ e può essere passato in funzioni:

template <typename ...Args>
void TraceLoc(const source_location& location, Args&& ...args) {
    std::ostringstream stream;
    stream << location.file_name() 
           << "(" << location.line()
           << ", function " << location.function_name() << "): ";
    (stream << ... << std::forward<Args>(args)) << '\n';

    std::cout << stream.str();
}
 
int main() {
    TraceLoc(source_location::current(), "hello world ", 10, ", ", 42);
}

Il codice sopra potrebbe generare:

main.cpp(22, function main): hello world 10, 42

Dai un'occhiata al codice su Wandbox

Maggiori informazioni nella proposta:P1208.

E nel mio articolo separato:Miglioramento della registrazione di stampa con informazioni sulla posizione della linea e storie C++ moderne - C++.

17. std::bind_front - per l'applicazione di funzioni parziali

P0356R5 e P1651R0

È un miglioramento per std::bind per l'applicazione di funzioni parziali. È più facile da usare e ha una sintassi più compatta:

using namespace std::placeholders;
auto f1 = std::bind(func, 42, 128, _1,_2);
// vs
auto f2 = std::bind_front(func, 42, 128);
        
f1(100, 200);
f2(100, 200);

Gioca con l'esempio @Compiler Explorer.

Inoltra perfettamente gli argomenti nell'oggetto richiamabile, ma contrariamente a std::bind non consente di riordinare gli argomenti.

Inoltre, bind_front è più leggibile e più facile da scrivere rispetto a un oggetto funzione lambda corrispondente. Per ottenere lo stesso risultato, la tua lambda dovrebbe supportare l'inoltro perfetto, la specifica dell'eccezione e altri codici standard.

  • c++ - Perché usare std::bind_front su lambda in C++ 20? - Overflow dello stack
  • in corda doppia / Suggerimento della settimana n. 108:evita std::bind

18. Ricerca eterogenea di contenitori non ordinati

In C++14, abbiamo trovato un modo per cercare una chiave in un contenitore ordinato per tipi che sono "paragonabili" alla chiave. Ciò ha consentito la ricerca tramite const char* in una mappa di std::string e in alcuni casi ha aggiunto potenziali miglioramenti della velocità.

C++20 colma il divario e aggiunge il supporto per i contenitori non ordinati come unordered_map o unorderd_set e altri.

Vedi l'esempio:

struct string_hash {
  using is_transparent = void;
  [[nodiscard]] size_t operator()(const char *txt) const {
    return std::hash<std::string_view>{}(txt);
  }
  [[nodiscard]] size_t operator()(std::string_view txt) const {
    return std::hash<std::string_view>{}(txt);
  }
  [[nodiscard]] size_t operator()(const std::string &txt) const {
    return std::hash<std::string>{}(txt);
  }
};

int main() {
  auto addIntoMap = [](auto &mp) {
    mp.emplace(std::make_pair("Hello Super Long String", 1));
    mp.emplace(std::make_pair("Another Longish String", 2));
    mp.emplace(std::make_pair("This cannot fall into SSO buffer", 3));
  };

  std::cout << "intMapNormal creation...\n";
  std::unordered_map<std::string, int> intMapNormal;
  addIntoMap(intMapNormal);

  std::cout << "Lookup in intMapNormal: \n";
  bool found = intMapNormal.contains("Hello Super Long String");
  std::cout << "Found: " << std::boolalpha << found << '\n';

  std::cout << "intMapTransparent creation...\n";
  std::unordered_map<std::string, int, string_hash, std::equal_to<>>
      intMapTransparent;
  addIntoMap(intMapTransparent);

  std::cout << "Lookup in map by const char*: \n";
  // this one won't create temp std::string object!
  found = intMapTransparent.contains("Hello Super Long String");
  std::cout << "Found: " << std::boolalpha << found << '\n';

  std::cout << "Lookup in map by string_view: \n";
  std::string_view sv("Another Longish String");
  // this one won't create temp std::string object!
  found = intMapTransparent.contains(sv);
  std::cout << "Found: " << std::boolalpha << found << '\n';
}

Gioca con il codice @Compiler Explorer

Per i contenitori ordinati, abbiamo solo bisogno di un oggetto funzione di confronto "trasparente". Nel caso di contenitori non ordinati, abbiamo bisogno anche di un hash per supportare i tipi compatibili.

La proposta iniziale P0919R3 e gli aggiornamenti finali:P1690R1.

Vedi il mio articolo separato su questa funzionalità (e anche da C++14):C++20:Eterogeneous Lookup in (Un)ordered Containers - C++ Stories.

19. Creazione intelligente del puntatore con inizializzazione predefinita

Quando assegni un array, puoi scrivere il codice seguente:

new T[]()
// vs
new T[]
  • Il primo è "inizializzazione del valore" e, per gli array, inizializza ogni elemento su zero (per i tipi predefiniti) o chiama i loro ctor predefiniti.
  • Quest'ultima è chiamata inizializzazione predefinita e, per i tipi predefiniti, genera valori indeterminati o chiama default ctor.

Per i buffer, è abbastanza comune non svuotare la memoria poiché potresti voler sovrascriverla immediatamente con altri dati (ad esempio, caricati da un file o da una rete).

Come appare quando avvolgi tale allocazione di array all'interno di un puntatore intelligente, quindi le attuali implementazioni di make_unique e make_shared utilizzato la prima forma di inizializzazione. E quindi, potresti vedere un piccolo sovraccarico di prestazioni.

Con C++20, abbiamo un'opzione per essere flessibili su tale inizializzazione e continuare a utilizzare in modo sicuro make_shared /make_unique .

Queste nuove funzioni si chiamano:

std::make_unique_for_overwrite
std::make_shared_for_overwrite
std::allocate_shared_for_overwrite

In C++20 puoi scrivere:

auto ptr = std::make_unique_for_overwrite<int[]>(COUNT);

Vorresti vedere di più?
Per vedere i benchmark, dai un'occhiata a questo post premium del blog per Patrons:"Smart Pointers Initialization Speedup in C++20 - Benchmarks" ed è disponibile per C++ Stories Premium/Patreon membri. Scopri tutti i vantaggi Premium qui.

Si veda il ragionamento e la proposta iniziale in P1020R1.

Nota a margine :questa funzione è stata votata come make_unique_default_init, ma il nome è stato cambiato in _for_overwrite nel documento:P1973R1.

E dai un'occhiata al mio articolo separato su:C++ Smart Pointers and Arrays - C++ Stories.

20. Confronti integrali sicuri

Quando confronti:

const long longVal = -100;
const size_t sizeVal = 100;
std::cout << std::boolalpha << (longVal < sizeVal);

Questo stampa false come longVal viene convertito in size_t e ora ha il valore di std::numeric_limits<size_t>::max()-100+1 . Vedi qui @Compiler Explorer.

A volte tali confronti senza segno e segno sono utili ed è per questo che in C++20 Nella libreria standard avremo le seguenti nuove funzioni nel <utility> intestazione:

template <class T, class U>
constexpr bool cmp_equal (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_not_equal (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_less (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_greater (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_less_equal (T t , U u) noexcept
template <class T, class U>
constexpr bool cmp_greater_equal (T t , U u) noexcept
template <class R, class T>
constexpr bool in_range (T t) noexcept

T e U devono essere tipi interi standard:(un)signed char, int, short, long, long long, uint8_t... . Queste funzioni non possono essere utilizzate per confrontare std::byte , char8_t , char16_t , char32_t , wchar_t e bool .

Con queste funzioni è possibile confrontare valori di diverso tipo con il significato “matematico”.

Ad esempio:

Possiamo riscrivere il nostro esempio in

const long longVal = -100;
const size_t sizeVal = 100;
std::cout << std::boolalpha;
std::cout << std::cmp_less(longVal, sizeVal); 

Vedi il codice in @Compiler Explorer.

E ora il codice stampa true .

Vedi di più nella proposta P0586

Bonus - altre fantastiche funzioni

Come accennato nell'introduzione, in C++20 abbiamo circa 70 funzionalità del linguaggio e 80 modifiche alla libreria. Di seguito puoi trovare una tabella con brevi note su altri elementi interessanti.

La lingua è la prima:

Caratteristica Note
Attributi [[likely]] e [[unlikely]] Vedi il mio articolo bonus su Patreon (gratuito) - Attributi C++, da C++11 a C++20
Rendi typename più opzionale Vedi il mio post sul blog separato:Semplifica il codice del modello con meno nomi di tipo in C++20 - Storie C++
Attributo [[no_unique_address]] Vedi il mio articolo:Ottimizzazione della classe base vuota, no_unique_address e unique_ptr - Storie C++
explicit(bool) La parola chiave esplicita può essere applicata in modo condizionale, utile per i tipi di template wrapper.
Macro di test delle funzionalità Macro standardizzate che descrivono se una determinata funzionalità è disponibile nel compilatore. Vedi Migliorare il codice multipiattaforma con __has_include e Macro di test delle funzionalità - Storie C++
Inizializzazione tra parentesi di aggregati Migliora la coerenza nel codice del modello! Ora puoi scrivere int ab[] (1, 2, 3);

E anche più parti della libreria:

Caratteristica Note
std::basic_osyncstream Uscita bufferizzata sincronizzata
std::to_address Ottieni l'indirizzo rappresentato da p in tutti i casi
std::lerp() e std::midpoint() Più funzioni numeriche!
std::to_array Consente una notazione più breve e la deduzione di tipo/dimensione
Funzione di manipolazione dei bit bit_cast , byteswap , bit_ceil , bit_width , popcount e più funzioni bit!

Riepilogo

In questo post del blog, spero che tu abbia trovato alcune funzionalità che potrebbero essere immediatamente applicate al tuo codice. Da cose di linguaggio più secondario come campi di bit e NSDMI a using enum o inizializzatore per il ciclo for basato sull'intervallo. E poi funzionalità della libreria come costanti matematiche, starts_with o ricerca eterogenea. La maggior parte delle aree per C++ sono coperte.

Se vuoi controllare tutte le funzionalità di C++20 supportate dal tuo compilatore, visita questa pratica pagina su cppreference:supporto del compilatore C++20.

Vedi l'articolo C++17 simile:17 Funzionalità C++17 più piccole ma utili - Storie C++.

Torna a te

  • Qual ​​è la tua funzionalità più piccola preferita di C++20?
  • Hai usato C++20 in produzione?

Partecipa alla discussione qui sotto nei commenti o nel seguente thread /reddit/r/cpp.