Concetti di C ++ 20: quale specializzazione di template viene scelta quando l'argomento template si qualifica per più concetti?


23

Dato:

#include <concepts>
#include <iostream>

template<class T>
struct wrapper;

template<std::signed_integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "signed_integral" << std::endl;
    }
};

template<std::integral T>
struct wrapper<T>
{
    wrapper() = default;
    void print()
    {
        std::cout << "integral" << std::endl;
    }
};

int main()
{
    wrapper<int> w;
    w.print(); // Output : signed_integral
    return 0;
}

Dal codice sopra, si intqualifica per entrambi std::integrale std::signed_integralconcetto.

Sorprendentemente, questo compila e stampa "signed_integral" su entrambi i compilatori GCC e MSVC. Mi aspettavo che fallisse con un errore sulla falsariga di "specializzazione di template già definita".

Ok, è legale, abbastanza giusto, ma perché è stato std::signed_integralscelto invece di std::integral? Esistono regole definite nello standard con quale specializzazione di modello viene scelta quando più concetti si qualificano per l'argomento modello?


Non direi che è legale solo per il fatto che i compilatori lo accettano, specialmente nelle prime fasi dell'adozione.
Slava,

@Slava in questo caso, i concetti sono attentamente progettati in modo che si susseguano in modo intuitivo
Guillaume Racicot

@GuillaumeRacicot va bene, ho appena commentato che la conclusione "è legale perché il compilatore l'ha accettata", diciamo fuorviante. Non ho detto che questo non è legale però.
Slava,

Risposte:


14

Questo perché i concetti possono essere più specializzati di altri, un po 'come il modello stesso si ordina. Questo si chiama ordinamento parziale dei vincoli

Nel caso dei concetti, si susseguono quando includono vincoli equivalenti. Ad esempio, ecco come std::integrale std::signed_integralsono implementati:

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T> //   v--------------v---- Using the contraint defined above
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

Normalizzando i vincoli il compilatore riduce l'espressione del contrappunto a questo:

template<typename T>
concept integral = std::is_integral_v<T>;

template<typename T>
concept signed_integral = std::is_integral_v<T> && std::is_signed_v<T>;

In questo esempio, signed_integralimplica integralcompletamente. Quindi, in un certo senso, un integrale firmato è "più vincolato" di un integrale.

Lo standard lo scrive in questo modo:

Da [temp.func.order] / 2 (sottolineatura mia):

L'ordinamento parziale seleziona quale dei due modelli di funzione è più specializzato dell'altro trasformando ciascun modello a turno (vedere il paragrafo successivo) ed eseguendo la deduzione dell'argomento del modello utilizzando il tipo di funzione. Il processo di detrazione determina se uno dei modelli è più specializzato dell'altro. In tal caso, il modello più specializzato è quello scelto dal processo di ordinazione parziale. Se entrambe le detrazioni hanno esito positivo, l'ordinamento parziale seleziona il modello più vincolato come descritto dalle regole in [temp.constr.order] .

Ciò significa che se esiste una sostituzione multipla possibile per un modello ed entrambi sono scelti dall'ordinamento parziale, selezionerà il modello più vincolato.

Da [temp.constr.order] / 1 :

Un vincolo P sottrae un vincolo Q se e solo se, per ogni clausola disgiuntiva P i nella forma disgiuntiva normale di P , P i sussegue ogni clausola congiuntiva Q j nella forma congiuntiva normale di Q , dove

  • una clausola disgiuntiva P i sussegue una clausola congiuntiva Q j se e solo se esiste un vincolo atomico P ia in P i per il quale esiste un vincolo atomico Q jb in Q j tale che P ia sottrae Q jb , e

  • un vincolo atomico A sottrae un altro vincolo atomico B se e solo se A e B sono identici usando le regole descritte in [temp.constr.atomic] .

Descrive l'algoritmo di sussunzione utilizzato dal compilatore per ordinare i vincoli e quindi i concetti.


2
Sembra che tu
stia per finire

11

C ++ 20 ha un meccanismo per decidere quando una particolare entità vincolata è "più vincolata" di un'altra. Questa non è una cosa semplice

Questo inizia con il concetto di scomporre un vincolo nei suoi componenti atomici, un processo chiamato normalizzazione dei vincoli . È grande e troppo complesso per entrare qui, ma l'idea di base è che ogni espressione in un vincolo è suddivisa in pezzi concettuali atomici, ricorsivamente, fino a raggiungere una sottoespressione componente che non è un concetto.

Detto questo, diamo un'occhiata a come sono definiti i concetti integrale :signed_integral

template<class T>
  concept integral = is_integral_v<T>;
template<class T>
  concept signed_­integral = integral<T> && is_signed_v<T>;

La decomposizione di integralè giusta is_integral_v. La decomposizione di signed_integralè is_integral_v && is_signed_v.

Veniamo ora al concetto di sussunzione di vincoli . È un po 'complicato, ma l'idea di base è che si dice che un vincolo C1 "sussume" un vincolo C2 se la decomposizione di C1 contiene ogni sottoespressione in C2. Possiamo vedere che integralnon mlo signed_integral, ma signed_integral lo fa subsume integral, in quanto contiene tutto ciò integralfa.

Successivamente, arriviamo all'ordinamento di entità vincolate:

Una dichiarazione D1 è vincolata almeno quanto una dichiarazione D2 se * D1 e D2 sono entrambe dichiarazioni vincolate e i vincoli associati di D1 sono inferiori a quelli di D2; o * D2 non ha vincoli associati.

Perché signed_integralsussume integral, <signed_integral> wrapperè "almeno vincolato" come <integral> wrapper. Tuttavia, non è vero il contrario, poiché la sussunzione non è reversibile.

Pertanto, in accordo con la regola per le entità "più vincolate":

Una dichiarazione D1 è più vincolata di un'altra dichiarazione D2 quando D1 è vincolata almeno come D2 e ​​D2 non è vincolata almeno come D1.

Poiché il <integral> wrappernon è almeno vincolato come <signed_integral> wrapper, quest'ultimo è considerato più vincolato del primo.

E quindi, quando entrambi potrebbero applicarsi, vince la dichiarazione più vincolata.


Tenere presente che le regole della sottosezione di vincolo si interrompono quando si incontra un'espressione che non è a concept. Quindi se hai fatto questo:

template<typename T>
constexpr bool my_is_integral_v = std::is_integral_v<T>;

template<typename T>
concept my_signed_integral = my_is_integral_v<T> && std::is_signed_v<T>;

In questo caso, my_signed_integral non si riassumerebbe std::integral. Anche se my_is_integral_vè definito in modo identico std::is_integral_v, poiché non è un concetto, le regole di assunzione di C ++ non possono scrutare attraverso di esso per determinare che sono uguali.

Quindi le regole di sussunzione ti incoraggiano a costruire concetti fuori dalle operazioni su concetti atomici.


3

Con Partial_ordering_of_constraints

Si dice che un vincolo P subordina il vincolo Q se si può dimostrare che P implica Q fino all'identità dei vincoli atomici in P e Q.

e

La relazione di assunzione definisce l'ordine parziale dei vincoli, che viene utilizzato per determinare:

  • il miglior candidato possibile per una funzione non modello nella risoluzione di sovraccarico
  • l'indirizzo di una funzione non modello in un set di sovraccarico
  • la migliore corrispondenza per un argomento del modello di modello
  • ordinamento parziale delle specializzazioni dei modelli di classe
  • ordinamento parziale dei modelli di funzione

E il concetto std::signed_integralcomprende il std::integral<T>concetto:

template < class T >
concept signed_integral = std::integral<T> && std::is_signed_v<T>;

Quindi il tuo codice è ok, in quanto std::signed_integralè più "specializzato".

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.