Perché viene scelto questo sovraccarico di un operatore di conversione?


27

Considera il seguente codice .

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

Qui il secondo operatore di conversione viene scelto dalla risoluzione di sovraccarico. Perché?

Per quanto ho capito, i due operatori sono dedotti rispettivamente operator int &&() conste operator int &() constrispettivamente. Entrambi sono nel set di funzioni vitali. Leggere [over.match.best] non mi ha aiutato a capire perché quest'ultimo sia migliore.

Perché la seconda funzione è migliore della prima?


Sono un gatto semplice e direi che deve essere la seconda volta che l'introduzione dell'altra sarebbe stata una svolta nel C ++ 11. Sarà nello standard da qualche parte. Buona domanda però, avere un voto. (Esperti di attenzione: se questo commento è hogwash, per favore, dimmelo!)
Bathsheba,

3
FWIW, template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;riesce a chiamare il primo.
NathanOliver,

5
@LightnessRaceswithMonica Gli operatori di conversione lo consentono, altrimenti non ci sarebbe modo di avere più operatori di conversione.
NathanOliver,

1
@Bathsheba Non credo sia questo il motivo. È come dire che i costruttori di mosse non possono mai essere selezionati in base alla risoluzione del sovraccarico perché sarebbe un cambiamento decisivo. Se scrivi un costruttore di spostamento, stai optando per il tuo codice che viene "rotto" da quella definizione.
Brian,

1
@LightnessRaceswithMonica Il modo in cui lo mantengo dritto nella mia testa è che tratto il tipo restituito come il nome della funzione. Tipi diversi equivalgono a nomi diversi equivalgono a successo :)
NathanOliver

Risposte:


13

L'operatore di conversione che restituisce T&è preferito perché è più specializzato dell'operatore di conversione che restituisce T&&.

Vedi C ++ 17 [temp.deduct.partial] / (3.2):

Nel contesto di una chiamata a una funzione di conversione, vengono utilizzati i tipi restituiti dei modelli di funzione di conversione.

e / 9:

Se, per un determinato tipo, la detrazione ha esito positivo in entrambe le direzioni (ovvero, i tipi sono identici dopo le trasformazioni precedenti) ed entrambi Pe Asono stati tipi di riferimento (prima di essere sostituiti con il tipo di cui sopra): - se il tipo dal modello argomento era un riferimento al valore e il tipo dal modello di parametro non lo era, il tipo di parametro non è considerato almeno specializzato come il tipo di argomento; ...


12

Gli operatori di conversione del valore restituito dedotti sono un po 'strani. Ma l'idea di base è che si comporta come un parametro di funzione per scegliere quale viene utilizzato.

E quando si decide tra T&&e T&le T&vittorie nelle regole di risoluzione del sovraccarico. Questo per consentire:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

lavorare. T&&può corrispondere a un valore nominale, ma quando sono disponibili sia i valori limite di sovraccarico sia quelli di riferimento universali, è preferibile quello di valore superiore.

Il giusto set di operatori di conversione è probabilmente:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

o anche

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

per evitare che l'estensione della vita non riuscita ti morda.

3 I tipi utilizzati per determinare l'ordinamento dipendono dal contesto in cui viene eseguito l'ordinamento parziale:

[Omissis]

(3.2) Nel contesto di una chiamata a una funzione di conversione, vengono utilizzati i tipi restituiti dei modelli di funzione di conversione.

Che poi finisce a seconda delle regole "più specializzate" quando si selezionano i sovraccarichi:

(9.1) se il tipo dal modello di argomento era un riferimento lvalue e il tipo dal modello di parametro non lo era, il tipo di parametro non è considerato almeno specializzato come il tipo di argomento; altrimenti,

Quindi operator T&&non è almeno così specializzato come operator T&, nel frattempo nessun stato regola operator T&non è almeno così specializzato come operator T&&, quindi operator T&è più specializzato di operator T&&.

Modelli più specializzati vincono la risoluzione del sovraccarico su meno, tutto il resto è uguale.


Qualcuno ha aggiunto il tag di avvocato linguistico mentre rispondevo; Potrei semplicemente cancellare. Ho una vaga memoria di leggere il testo che afferma che è trattato come un parametro per scegliere quale viene chiamato, e il testo che parla di come viene trattato &&vs &quando si selezionano i sovraccarichi del modello, ma prendere in giro il testo esatto richiederebbe un po 'di tempo .
Yakk - Adam Nevraumont,

4

Stiamo provando a inizializzare un intda un any. Il processo per questo:

  1. Capire tutto il modo in cui possiamo farlo. Cioè, determinare tutti i nostri candidati. Questi provengono da funzioni di conversione non esplicite che possono essere convertite in intuna sequenza di conversione standard ( [over.match.conv] ). La sezione contiene questa frase:

    Una chiamata a una funzione di conversione che restituisce "riferimento a X" è un valore di tipo Xe una tale funzione di conversione viene quindi considerata come resa Xper questo processo di selezione delle funzioni candidate.

  2. Scegli il miglior candidato.

Dopo il passaggio 1, abbiamo due candidati. operator int&() conste operator int&&() const, entrambi i quali sono considerati intper lo scopo di selezionare le funzioni candidate. Qual è la migliore candidata int?

Abbiamo un tiebreaker che preferisce i riferimenti lvalue ai riferimenti rvalue ( [over.ics.rank] /3.2.3 ). Non stiamo davvero vincolando un riferimento qui, e l'esempio è in qualche modo invertito - questo è per il caso in cui il parametro è un riferimento lvalue vs rvalue.

Se ciò non si applica, passiamo al tiebreaker [over.match.best] /2.5 che preferisce il modello di funzione più specializzato.

In generale, la regola empirica è la conversione più specifica è la migliore corrispondenza. La funzione di conversione del riferimento lvalue è più specifica della funzione di conversione del riferimento di inoltro, quindi è preferita. Non c'è nulla intdell'inizializzazione che richiede un valore (se invece avessimo inizializzato un int&&, allora operator T&() constnon sarebbe stato un candidato).


3.2.3 dice davvero che T&&vince no? Ah, ma non abbiamo un valore da vincolare. O noi? Quando abbiamo scelto i nostri candidati, alcune parole devono aver definito il modo in cui abbiamo ottenuto , int&ed è int&&stato vincolante?
Yakk - Adam Nevraumont,

@ Yakk-AdamNevraumont No, che si preferiscono i riferimenti ai valori di riferimento ai riferimenti di valore. Penso che comunque sia probabilmente la sezione sbagliata, e il tiebreaker "più specializzato" è quello corretto.
Barry,
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.