Perché il concetto same_as verifica il tipo di uguaglianza due volte?


19

Osservando la possibile implementazione del concetto same_as su https://en.cppreference.com/w/cpp/concepts/same_as ho notato che sta accadendo qualcosa di strano.

namespace detail {
    template< class T, class U >
    concept SameHelper = std::is_same_v<T, U>;
}

template< class T, class U >
concept same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;

La prima domanda è: perché un SameHelperconcetto è unito? Il secondo è perché same_ascontrolla se Tè uguale Ue Uuguale a T? Non è ridondante?


Solo perché SameHelper<T, U>potrebbe essere vero, non significa che SameHelper<U, T>potrebbe esserlo.
Un tizio programmatore il

1
questo è il punto, se a è uguale a b, b è uguale a a non è vero?
user7769147,

@ user7769147 Sì, e questo sta definendo quella relazione.
François Andrieux,

4
Hmm la documentazione per std :: is_same dice anche "La commutatività è soddisfatta, vale a dire per due tipi T e U, is_same<T, U>::value == truese e solo se is_same<U, T>::value == true". Ciò implica che questo doppio controllo non è necessario
Kevin,

1
No, questo è sbagliato, lo std :: is_same dice: se e solo se la condizione è valida, due tipi sono commutativi. Non é necessariamente così. Ma non riesco a trovare l'esempio di due tipi non commutativi.
Nemanja Boric,

Risposte:


16

Domanda interessante. Di recente ho visto il discorso di Andrew Sutton su Concepts e nella sessione di domande e risposte qualcuno ha posto la seguente domanda (data e ora nel seguente link): CppCon 2018: Andrew Sutton "Concepts in 60: Tutto ciò che devi sapere e niente che non sai"

Quindi la domanda si riduce a: If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?Andrew ha risposto di sì, ma ha sottolineato il fatto che il compilatore ha alcuni metodi interni (che sono trasparenti per l'utente) per scomporre i concetti in proposizioni logiche atomiche ( atomic constraintscome Andrew ha formulato il termine) e verificare se sono equivalente.

Ora guarda cosa dice cppreference su std::same_as:

std::same_as<T, U>subsume std::same_as<U, T>e viceversa.

È fondamentalmente una relazione "if-and-only-if": si implicano a vicenda. (Equivalenza logica)

La mia congettura è che qui ci sono i vincoli atomici std::is_same_v<T, U>. Il modo in cui i compilatori trattano std::is_same_vpotrebbe farli pensare std::is_same_v<T, U>e std::is_same_v<U, T>come due vincoli diversi (sono entità diverse!). Quindi, se si implementa std::same_asutilizzando solo uno di essi:

template< class T, class U >
concept same_as = detail::SameHelper<T, U>;

Poi std::same_as<T, U>e std::same_as<U, T>avrebbe "esplodere" a diversi vincoli atomiche e diventare non equivalenti.

Bene, perché al compilatore interessa?

Considera questo esempio :

#include <type_traits>
#include <iostream>
#include <concepts>

template< class T, class U >
concept SameHelper = std::is_same_v<T, U>;

template< class T, class U >
concept my_same_as = SameHelper<T, U>;

// template< class T, class U >
// concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;

template< class T, class U> requires my_same_as<U, T>
void foo(T a, U b) {
    std::cout << "Not integral" << std::endl;
}

template< class T, class U> requires (my_same_as<T, U> && std::integral<T>)
void foo(T a, U b) {
    std::cout << "Integral" << std::endl;
}

int main() {
    foo(1, 2);
    return 0;
}

Idealmente, my_same_as<T, U> && std::integral<T>sussume my_same_as<U, T>; pertanto, il compilatore dovrebbe selezionare la seconda specializzazione del modello, tranne ... non lo fa: il compilatore genera un errore error: call of overloaded 'foo(int, int)' is ambiguous.

La ragione di ciò è che da allora my_same_as<U, T>e my_same_as<T, U>non si susseguono a vicenda my_same_as<T, U> && std::integral<T>e my_same_as<U, T>diventano incomparabili (sull'insieme parzialmente ordinato di vincoli in relazione alla sussunzione).

Tuttavia, se si sostituisce

template< class T, class U >
concept my_same_as = SameHelper<T, U>;

con

template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;

Il codice viene compilato.


same_as <T, U> e same_as <U, T> potrebbero anche essere diversi contorni atomici ma il loro risultato sarebbe sempre lo stesso. Perché il compilatore si preoccupa così tanto di definire same_as come due diversi vincoli atomici che da un punto di vista logico sono gli stessi?
user7769147

2
Il compilatore deve considerare due espressioni qualsiasi come distinte per l'assunzione di vincoli, ma può considerare gli argomenti in modo ovvio. Quindi non solo abbiamo bisogno di entrambe le direzioni (in modo che non contenga l'ordine in cui sono nominati quando si confrontano i vincoli), ma abbiamo anche bisogno SameHelper: fa sì che i due usi di is_same_vderivino dalla stessa espressione.
Davis Herring,

@ user7769147 Vedi risposta aggiornata.
Rin Kaenbyou,

1
Sembra che la saggezza convenzionale sia errata per quanto riguarda l'uguaglianza dei concetti. A differenza dei modelli in cui is_same<T, U>è identico is_same<U, T>, due vincoli atomici non sono considerati identici a meno che non siano formati anche dalla stessa espressione. Da qui la necessità di entrambi.
AndyG,

Che dire are_same_as? template<typename T, typename U0, typename... Un> concept are_same_as = SameAs<T, U0> && (SameAs<T, Un> && ...);fallirebbe in alcuni casi. Ad esempio are_same_as<T, U, int>sarebbe equivalente are_same_as<T, int, U>ma non aare_same_as<U, T, int>
user7769147

2

std::is_same è definito come vero se e solo se:

T e U nominano lo stesso tipo con le stesse qualifiche cv

Per quanto ne so, lo standard non definisce il significato di "stesso tipo", ma nel linguaggio naturale e nella logica "stesso" è una relazione di equivalenza e quindi è commutativa.

Alla luce di questo presupposto, a cui attribuisco, is_same_v<T, U> && is_same_v<U, V>sarebbe davvero ridondante. Ma same_­asnon è specificato in termini di is_same_v; questo è solo per esposizione.

Il controllo esplicito per entrambi consente l'implementazione per same-as-implsoddisfare same_­assenza essere commutativo. Specificandolo in questo modo viene descritto esattamente come si comporta il concetto senza limitare il modo in cui potrebbe essere implementato.

is_same_vNon so perché questo approccio sia stato scelto invece di specificare in termini di . Un vantaggio dell'approccio scelto è senza dubbio il fatto che le due definizioni sono disaccoppiate. Uno non dipende dall'altro.


2
Sono d'accordo con te, ma quest'ultima discussione è un po 'allungata. A me sembra: "Ehi, ho questo componente riutilizzabile che mi dice se due tipi sono uguali. Ora ho questo altro componente che deve sapere se i tipi sono uguali, ma, invece di riutilizzare il mio componente precedente , Creerò solo una soluzione ad hoc specifica per questo caso. Ora ho "disaccoppiato" il ragazzo che ha bisogno della definizione di uguaglianza dal ragazzo che ha la definizione di uguaglianza. Già! "
Cássio Renan,

1
@ CássioRenan Certo. Come ho detto, non so perché, questo è il miglior ragionamento che potrei inventare. Gli autori potrebbero avere una logica migliore.
Eerorika,
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.