Perché il C ++ non può dedurre T in una chiamata a Foo <T> :: Foo (T &&)?


9

Dato il seguente modello struttura:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

Questo viene compilato e Tsi deduce che sia int:

auto f = Foo(2);

Ma questo non viene compilato: https://godbolt.org/z/hAA9TE

int x = 2;
auto f = Foo(x);

/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
    auto f = Foo(x);
             ^

<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
    Foo(T&&) {}
    ^
*/

Tuttavia, Foo<int&>(x)è accettato.

Ma quando aggiungo una guida alla detrazione definita dall'utente apparentemente ridondante, funziona:

template<typename T>
Foo(T&&) -> Foo<T>;

Perché non può Tessere dedotto int&senza una guida alla detrazione definita dall'utente?



Questa domanda sembra riguardare tipi di template comeFoo<T<A>>
jtbandes del

Risposte:


5

Penso che la confusione qui sorga perché esiste un'eccezione specifica per le guide di deduzione sintetizzate relative ai riferimenti di inoltro.

È vero che la funzione candidata ai fini della detrazione dell'argomento del modello di classe generata dal costruttore e quella generata dalla guida alla detrazione definita dall'utente sembrano esattamente uguali, ovvero:

template<typename T>
auto f(T&&) -> Foo<T>;

ma per quello generato dal costruttore, T&&è un semplice riferimento di valore, mentre è un riferimento di inoltro nel caso definito dall'utente. Questo è specificato da [temp.deduct.call] / 3 della norma C ++ 17 (bozza N4659, enfatizza la mia):

Un riferimento di inoltro è un riferimento di valore a un parametro di modello cv non qualificato che non rappresenta un parametro di modello di un modello di classe (durante la deduzione dell'argomento del modello di classe ([over.match.class.deduct])).

Pertanto, il candidato sintetizzato dal costruttore della classe non dedurrà Tcome se da un riferimento di inoltro (che potrebbe dedurre Tessere un riferimento lvalue, quindi che T&&è anche un riferimento lvalue), ma invece dedurrà solo Tcome non-riferimento, quindi T&&è sempre un riferimento di valore.


1
Grazie per la risposta chiara e concisa. Sai perché esiste un'eccezione per i parametri del modello di classe nelle regole per l'inoltro dei riferimenti?
jtbandes

2
@jtbandes Questo sembra essere stato modificato a seguito di un commento da parte dell'ente nazionale statunitense, vedi documento p0512r0 . Non sono riuscito a trovare il commento però. La mia ipotesi per la logica è che se si scrive un costruttore che prende un riferimento di valore, di solito ci si aspetta che funzioni allo stesso modo se si specifica un Foo<int>(...)o giusto Foo(...), il che non è il caso dei riferimenti di inoltro (che potrebbero Foo<int&>invece dedurre .
Walnut

6

Il problema qui è che, dal momento che la classe è templato su T, nel costruttore Foo(T&&)stiamo non eseguendo tipo detrazione; Abbiamo sempre un riferimento di valore r. Cioè, il costruttore per Fooeffettivamente assomiglia a questo:

Foo(int&&)

Foo(2)funziona perché 2è un valore.

Foo(x)non perché xè un valore che non può essere associato int&&. Si potrebbe fare std::move(x)per lanciarlo nel tipo appropriato ( demo )

Foo<int&>(x)funziona bene perché il costruttore diventa Foo(int&)dovuto alle regole di crollo di riferimento; inizialmente è quello Foo((int&)&&)che crolla Foo(int&)secondo lo standard.

Per quanto riguarda la tua guida alla detrazione "ridondante": inizialmente esiste una guida alla detrazione del modello predefinita per il codice che sostanzialmente agisce come una funzione di supporto in questo modo:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
   return Foo<T>(std::move(value));
}

//... 
auto f = MakeFoo(x);

Questo perché lo standard impone che questo metodo (fittizio) di modello abbia gli stessi parametri del modello della classe (Just T) seguito da tutti i parametri del modello del costruttore (nessuno in questo caso; il costruttore non è modellato). Quindi, i tipi dei parametri della funzione sono gli stessi di quelli nel costruttore. Nel nostro caso, dopo un'istanza Foo<int>, il costruttore assomiglia a Foo(int&&)un riferimento di valore in altre parole. Da qui l'utilizzo di cui add_rvalue_reference_tsopra.

Ovviamente questo non funziona.

Quando hai aggiunto la tua guida alla detrazione "ridondante":

template<typename T>
Foo(T&&) -> Foo<T>;

È permesso il compilatore di distinguere che, nonostante qualsiasi tipo di riferimento collegato a Tnel costruttore ( int&, const int&, o int&&, ecc), si intende il tipo di dedurre per la classe di essere senza di riferimento (solo T). Questo perché improvvisamente ci stiamo eseguendo l'inferenza dei tipi.

Ora generiamo un'altra funzione di supporto (fittizia) che assomiglia a questa:

template<class U>
Foo<U> MakeFoo(U&& u)
{
   return Foo<U>(std::forward<U>(u));
}

// ...
auto f = MakeFoo(x);

(Le nostre chiamate al costruttore vengono reindirizzate alla funzione di supporto ai fini della deduzione dell'argomento del modello di classe, così Foo(x)diventa MakeFoo(x)).

Questo permette U&&di diventare int&e Tdiventare semplicementeint


Il secondo livello di modello non sembra necessario; che valore fornisce? Potete fornire un link ad alcuni documenti che chiariscono perché T&& è sempre trattato come riferimento di valore qui?
jtbandes

1
Ma se T non è stato ancora dedotto, cosa significa "qualsiasi tipo convertibile in T"?
jtbandes

1
Sei pronto a offrire una soluzione alternativa, ma puoi concentrarti maggiormente sulla spiegazione del motivo per cui non funziona se non lo modifichi in qualche modo? Perché non funziona così com'è? " xè un lvalue a cui non si può legare int&&" ma qualcuno che non capisce sarà perplesso che Foo<int&>(x)potrebbe funzionare ma non è stato capito automaticamente - penso che tutti vogliamo una comprensione più profonda del perché.
Wyck

2
@Wyck: ho aggiornato il post per concentrarmi maggiormente sul perché.
AndyG

3
@Wyck Il vero motivo è molto semplice: lo standard ha specificamente disattivato la semantica di inoltro perfetto nelle guide di deduzione sintetizzate per evitare sorprese.
LF
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.