Rilevo un malinteso minore nella comprensione del caso d'uso che state affrontando.
Prima di tutto, questo è un modello di funzione:
struct A
{
template <typename... Args>
void f(Args... args)
{
}
};
E questo non è un modello di funzione:
template <typename... Args>
struct A
{
void f(Args... args)
{
}
};
Nel primo definizione (con un modello di funzione) il tipo di argomento detrazione ha luogo. Nel secondo caso, non vi è alcun tipo di detrazione.
Non si sta utilizzando un modello di funzione. Stai utilizzando una funzione membro non modello da un modello di classe e per questa particolare funzione membro la sua firma è fissa.
Definendo la classe trap
come di seguito:
template <typename T, T t>
struct trap;
template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{
static R call(Args... args);
};
e in riferimento alla funzione membro come di seguito:
&trap<decltype(&Base::target), &Base::target>::call;
si finisce con un puntatore a una funzione statica non-template call
con una firma fissa, identica alla firma della funzione target
.
Ora, la funzione call
funge da invocatore intermedio. Sarete chiamando la funzione call
, e che la funzione sarà chiamare la funzione target
membro, passando le proprie argomentazioni per inizializzare i parametri s' target
, dicono:
template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{
static R call(Args... args)
{
return (get_base()->*t)(args...);
}
};
Supponiamo che la funzione di target
utilizzato per istanziare il modello trap
classe è definiti come segue:
struct Base
{
int target(Noisy& a, Noisy b);
};
istanziando classe trap
si finisce con la seguente funzione call
:
// what the compiler *sees*
static int call(Noisy& a, Noisy b)
{
return get_base()->target(a, b);
}
Fortunatamente, a
è passato con riferimento, si tratta solo di inoltrato e vincolati da uno stesso tipo di riferimento nel parametro 's il target
. Purtroppo, questo non vale per l'oggetto b
- non importa se la classe Noisy
è mobile o no, si sta facendo più copie dell'istanza b
, dal momento che uno è passato per valore :
DEMO 1
Questo è un po 'inefficiente: si avrebbe potuto salvare almeno una chiamata costruttore di copia, trasformandolo in una chiamata mossa costruttore se solo si potrebbe trasformare l'istanza b
in un xValue :
Ora chiamerebbe invece un costruttore di spostamento per il secondo parametro.
Fin qui tutto bene, ma è stato eseguito manualmente (std::move
aggiunto sapendo che è sicuro applicare la semantica del movimento).Ora, la domanda è, come potrebbe la stessa funzionalità essere applicata quando si opera su un parametro di pacchetto:?
return get_base()->target(std::move(args)...); // WRONG!
Non è possibile applicare std::move
chiamata a ogni argomento all'interno del parametro confezione. Ciò probabilmente causerebbe errori del compilatore se applicato in modo uguale a tutti gli argomenti.
DEMO 2
Fortunatamente, anche se non è un Args...
inoltro riferimento, la funzione std::forward
assistente può essere usato al posto. Cioè, a seconda del tipo <T>
è std::forward<T>
(un riferimento lvalue o un non-lvalue-riferimento) il std::forward
si comportano in modo diverso:
riferimenti lvalue (ad esempio se T
è Noisy&
): il valore la categoria dell'espressione rimane un lvalue (ovvero Noisy&
).
ai non lvalue riferimenti (ad esempio, se è T
Noisy&&
o un semplice Noisy
): la categoria valore dell'espressione diventa xValue (cioè Noisy&&
).
Dopo aver detto questo, definendo la funzione target
come di seguito:
static R call(Args... args)
{
return (get_base()->*t)(std::forward<Args>(args)...);
}
si finisce con:
static int call(Noisy& a, Noisy b)
{
// what the compiler *sees*
return get_base()->target(std::forward<Noisy&>(a), std::forward<Noisy>(b));
}
girando la categoria valore dell'espressione che coinvolge b
in un xValue di b
, che è Noisy&&
. Ciò consente al compilatore di selezionare il costruttore di movimento per inizializzare il secondo parametro della funzione target
, lasciando intatto lo a
.
DEMO 3(confrontare l'output con DEMO 1)
Fondamentalmente, questo è ciò che il std::forward
è per. Solitamente, lo std::forward
viene utilizzato con un numero di inoltro , dove T
contiene il tipo dedotto in base alle regole di deduzione del tipo per i riferimenti di inoltro. Si noti che richiede sempre che si passi esplicitamente la parte <T>
, poiché applicherà un comportamento diverso a seconda del tipo (non dipende dalla categoria di valore del suo argomento). Senza l'argomento modello di tipo esplicito <T>
, std::forward
dedurrebbe sempre i riferimenti lvalue per gli argomenti a cui fanno riferimento i nomi (come quando si espande il pacchetto di parametri).
Ora, si voleva inoltre convertire alcuni degli argomenti da un tipo all'altro, mentre si inoltra tutti gli altri.Se non vi interessa circa il trucco con std::forward
argomenti ing dal parametro di pacchetto, e va bene a chiamare sempre un costruttore di copia, quindi la versione è OK:
template <typename T> // transparent function
T&& process(T&& t) {
return std::forward<T>(t);
}
Bar process(Foo x) { // overload for specific type of arguments
return Bar{x};
}
//...
get_base()->target(process(args)...);
DEMO 4
Tuttavia , se si vuole evitare la copia di quella Noisy
argomento nella demo, è necessario combinare in qualche modo std::forward
chiamata con il process
chiamata, e passaggio sopra i Args
tipi, in modo che std::forward
potrebbe applicarsi comportamento corretto (t urning in xvalues o non fare nulla). Ti ho appena dato un semplice esempio di come questo potrebbe essere implementato:
template <typename T, typename U>
T&& process(U&& u) {
return std::forward<T>(std::forward<U>(u));
}
template <typename T>
Bar process(Foo x) {
return Bar{x};
}
//...
get_base()->target(process<Args>(args)...);
Ma questa è solo una delle opzioni. Si può essere semplificata, riprodotte o riordinate, in modo che std::forward
è chiamato prima di chiamare la process
funzioni (la versione):
get_base()->target(process(std::forward<Args>(args))...);
DEMO 5(confrontare l'output con DEMO 4)
Ed funzionerà bene (cioè con la tua versione). Quindi il punto è che l'ulteriore std::forward
è solo per ottimizzare un po 'il tuo codice, e lo provided idea era solo una delle possibili implementazioni di quella funzionalità (come puoi vedere, produce lo stesso effetto).
In quegli esempi manca il contesto in cui viene invocata la funzione del processo, sempre con un argomento modello di tipo esplicito (almeno era l'idea di trasformare i riferimenti non-lvalue in xvalues). Puoi farcela con la tua versione, ma chiamerà sempre copy ctor durante l'inizializzazione dei parametri della funzione target –
Sono d'accordo con la versione 1 che è in qualche modo "sporca", perché è un sovraccarico di una funzione non basata su un modello e differiscono solo per tipo di ritorno. Ma per la versione 2 non riesco a vedere alcun senso. Dovresti sempre specificare il primo parametro del template, in quanto il compilatore non può dedurlo in un contesto di utilizzo. –
@PiotrS., Ho modificato per fornire un contesto (penso che corrisponda al mio caso d'uso reale). Grazie al tuo aiuto inestimabile in questi ultimi due giorni, ora ho una soluzione funzionante ([qui] (http://stackoverflow.com/q/27866483/435129)). Questo è l'ultimo componente che sto masticando perché non mi piace incorporare codice che non capisco. –