12

consideri questo frammento di codice, che utilizza il linguaggio comune avente un modello di funzione costrutto un'istanza di una classe template specializzato su un tipo dedotta, come visto con std::make_unique e std::make_tuple, ad esempio:È un riferimento universale? Std :: forward ha senso qui?

template <typename T> 
struct foo 
{ 
    std::decay_t<T> v_; 
    foo(T&& v) : v_(std::forward<T>(v)) {} 
}; 

template <typename U> 
foo<U> make_foo(U&& v) 
{ 
    return { std::forward<U>(v) }; 
} 

Nel contesto dei "riferimenti universali" di Scott Meyers, l'argomento a make_foo è un riferimento universale perché il suo tipo è dove U è dedotto. L'argomento del costruttore di foo non è un riferimento universale perché sebbene il suo tipo sia T&&, è (in generale) non dedotto.

Ma nel caso in cui il costruttore di foo è chiamato da make_foo, si mi sembra che potrebbe dare un senso pensare al argomento al costruttore di foo come un riferimento universale, perché T è stato dedotta dal modello di funzione make_foo. Le stesse regole di compressione di riferimento verranno applicate in modo che il tipo di v sia lo stesso in entrambe le funzioni. In questo caso, si può affermare che sia T e U siano stati dedotti.

Quindi la mia domanda è duplice:

  • Ha senso pensare al argomento al costruttore di foo come un riferimento universale nei limitati casi in cui T è stata dedotta all'interno di un universale contesto di riferimento dal chiamante, come nel mio esempio?
  • Nel mio esempio, sono entrambi sensati gli usi di std::forward?

risposta

14

make_foo è nella stessa ballpark come "giusto", ma non è foo. Il costruttore foo attualmente solo accetta uno non dedotto, e l'inoltro probabilmente non è quello che intendi (ma vedi il commento di @ nosid). Tutto sommato, foo dovrebbero prendere un parametro di tipo, avere un costruttore su modelli, e la funzione di produttore dovrebbe fare il decadente:

template <typename T> 
struct foo 
{ 
    T v_; 

    template <typename U> 
    foo(U && u) : v_(std::forward<U>(u)) { } 
}; 

template <typename U> 
foo<typename std::decay<U>::type> make_foo(U && u) 
{ 
    return foo<typename std::decay<U>::type>(std::forward<U>(u)); 
} 

In C++ 14 la funzione di produttore diventa un po 'più semplice da scrivere:

template <typename U> 
auto make_foo(U && u) 
{ return foo<std::decay_t<U>>(std::forward<U>(u)); } 

il tuo codice è scritto ora, int a; make_foo(a); creerebbe un oggetto di tipo foo<int &>. Questo memorizzerebbe internamente un int, ma il suo costruttore accetterebbe solo un argomento int &. Al contrario, make_foo(std::move(a)) creerebbe un foo<int>.

Così come lo hai scritto, l'argomento del modello di classe determina la firma del costruttore. (Lo std::forward<T>(v) ha ancora senso in un modo pervertito (grazie a @nodis per averlo indicato), ma questo non è sicuramente "forwarding".)

Ciò è molto insolito. In genere, il modello di classe deve determinare il tipo di wrapping rilevante e il costruttore deve accettare tutto ciò che può essere utilizzato per creare il tipo wrapper, cioè il costruttore dovrebbe essere un modello di funzione.

+0

In alternativa, utilizzare 'std :: mossa (v)' all'interno del costruttore originale – TemplateRex

+0

@TemplateRex: I don' Voglio chiamarlo "alternativa", poiché non è chiaramente quello che l'OP voleva (da allora il produttore non avrebbe avuto senso ad accettare i riferimenti lvalue). –

+5

Il costruttore di 'foo ' accetta un riferimento lvalue. L'utilizzo di 'std :: forward ' potrebbe avere senso in questo caso. – nosid

1

Non c'è una definizione formale di "riferimento universale", ma lo definirei come:

Un universale di riferimento è un parametro di un modello di funzione con il tipo di [template-parameter] && , con l'intento che il parametro template possa essere dedotto dall'argomento function, e l'argomento sarà passato o dal riferimento lvalue o dal riferimento rvalue come appropriato.

Così in tale definizione, no, il parametro T&& v nel costruttore 's foo non è un riferimento universale.

Tuttavia, l'intero punto della frase "riferimento universale" è di fornire un modello o un modello a cui noi umani dobbiamo pensare mentre progettiamo, leggiamo e interpretiamo il codice. Ed è ragionevole e utile dire che "Quando make_foo chiama il costruttore di foo<U>, il parametro di modello T è stato dedotto dall'argomento a make_foo in un modo che consente al parametro costruttore T&& v di essere un riferimento di lvalue o un riferimento di rvalore come adeguata." Questo è abbastanza vicino allo stesso concetto che sarebbe bene passare alla richiesta: "Quando make_foo chiama il costruttore di foo<U>, il parametro costruttore T&& v è essenzialmente un riferimento universale."

Sì, entrambi gli usi di std::forward faranno ciò che intendete qui, consentendo al membro v_ di spostarsi dall'argomento make_foo se possibile o copiare altrimenti. Ma avere make_foo(my_str) restituire un foo<std::string&>, non un foo<std::string>, che contiene una copia di my_str è abbastanza sorprendente ....

Problemi correlati