Perché std :: swap non è contrassegnato come constexpr prima di C ++ 20?


14

In C ++ 20, std::swapdiventa una constexprfunzione.

So che la biblioteca standard è rimasta davvero indietro rispetto al linguaggio nel marcare le cose constexpr, ma nel 2017 <algorithm>era praticamente rappresentata come un mucchio di altre cose. Eppure - std::swapnon lo era. Ricordo vagamente che c'è qualche strano difetto del linguaggio che impedisce quella marcatura, ma dimentico i dettagli.

Qualcuno può spiegarlo in modo succinto e chiaro?

Motivazione: è necessario capire perché potrebbe essere una cattiva idea contrassegnare una std::swap()funzione simile constexprnel codice C ++ 11 / C ++ 14.

Risposte:


11

Lo strano problema del linguaggio è CWG 1581 :

La clausola 15 [special] è perfettamente chiara che le funzioni speciali dei membri sono definite implicitamente solo quando vengono utilizzate in modo errato. Questo crea un problema per le espressioni costanti in contesti non valutati:

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

Il problema qui è che non ci è permesso di definire implicitamente constexpr duration::duration(duration&&)in questo programma, quindi l'espressione nell'elenco di inizializzatori non è un'espressione costante (perché invoca una funzione constexpr che non è stata definita), quindi l'inizializzatore rinforzato contiene una conversione restrittiva , quindi il programma è mal formato.

Se decommentiamo la riga n. 1, il costruttore di mosse è implicitamente definito e il programma è valido. Questa azione inquietante a distanza è estremamente sfortunata. Le implementazioni differiscono su questo punto.

Puoi leggere il resto della descrizione del problema.

Una risoluzione per questo problema è stata adottata in P0859 ad Albuquerque nel 2017 (dopo la spedizione di C ++ 17). Quel problema era un blocco sia per poter avere un constexpr std::swap(risolto in P0879 ) sia un constexpr std::invoke(risolto in P1065 , che ha anche esempi CWG1581), entrambi per C ++ 20.


L'esempio più semplice da comprendere qui, a mio avviso, è il codice del rapporto bug LLVM indicato in P1065:

template<typename T>
int f(T x)
{
    return x.get();
}

template<typename T>
constexpr int g(T x)
{
    return x.get();
}

int main() {

  // O.K. The body of `f' is not required.
  decltype(f(0)) a;

  // Seems to instantiate the body of `g'
  // and results in an error.
  decltype(g(0)) b;

  return 0;
}

CWG1581 riguarda tutto quando vengono definite le funzioni membro constexpr e la risoluzione garantisce che siano definite solo quando utilizzate. Dopo P0859, quanto sopra è ben formato (il tipo di bè int).

Poiché std::swaped std::invokeentrambi hanno invocare verifica di funzioni membro (costruzione spostare / assegnazione nel primo e l'operatore di chiamata / chiamate surrogati in quest'ultimo), entrambi dipendevano la risoluzione di questo problema.


Quindi, perché CWG-1581 impedisce / rende indesiderabile contrassegnare una funzione di scambio come constexpr?
einpoklum,

3
@einpoklum swap richiede std::is_move_constructible_v<T> && std::is_move_assignable_v<T>è true. Ciò non può accadere se le funzioni dei membri speciali non sono ancora state generate.
NathanOliver,

@NathanOliver: aggiunto questo alla mia risposta.
einpoklum,

5

La ragione

(a causa di @NathanOliver)

Per consentire una constexprfunzione di scambio, è necessario verificare, prima di creare un'istanza del modello per questa funzione, che il tipo di scambio sia costruibile e assegnabile ai movimenti. Sfortunatamente, a causa di un difetto del linguaggio risolto solo in C ++ 20, non è possibile verificarlo, poiché le funzioni dei membri rilevanti potrebbero non essere state ancora definite, per quanto riguarda il compilatore.

La cronologia

  • 2016: Antony Polukhin presenta la proposta P0202 , per contrassegnare tutte le <algorithm>funzioni come constexpr.
  • Il gruppo di lavoro principale del comitato standard discute del difetto CWG-1581 . Questo problema ha reso problematico avere constexpr std::swap()e anche constexpr std::invoke()- vedere la spiegazione sopra.
  • 2017: Antony rivede alcune volte la sua proposta di esclusione std::swape alcuni altri costrutti, e questo viene accettato in C ++ 17.
  • 2017: una risoluzione per l'emissione di CWG-1581 viene presentata come P0859 e accettata dal comitato standard nel 2017 (ma dopo la spedizione di C ++ 17).
  • Fine del 2017: Antony presenta una proposta complementare, P0879 , per rendere std::swap()constexpr dopo la risoluzione di CWG-1581.
  • 2018: la proposta complementare viene accettata (?) In C ++ 20. Come sottolinea Barry, lo è anche la std::invoke()correzione constexpr .

Il tuo caso specifico

È possibile utilizzare lo constexprscambio se non si verifica la costruttibilità delle mosse e l'assegnabilità delle mosse, ma si verifica piuttosto direttamente qualche altra caratteristica dei tipi che garantisce ciò in particolare. es. solo tipi primitivi e nessuna classe o struttura. Oppure, in teoria, potresti rinunciare ai controlli e gestire semplicemente eventuali errori di compilazione che potresti riscontrare, e con un comportamento instabile che passa da un compilatore all'altro. In ogni caso, non sostituirlo std::swap()con quel genere di cose.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.