2015-01-09 6 views
5

L'attività consiste nel creare una funzione a singolo argomento che inoltra tutti i tipi tranne uno (Foo), che converte (in Bar).Funzione di inoltro selettivo

(supponiamo che esista una conversione da Foo a Bar).

Ecco la scenario di utilizzo:

template<typename Args...> 
void f(Args... args) 
{ 
    g(process<Args>(args)...); 
} 

(. Ho cercato di estrarre/semplificarlo dal contesto originale here - se ho fatto un errore, per favore qualcuno mi dica!)

Qui ci sono due possibili implementazioni:

template<typename T> 
T&& process(T&& t) { 
    return std::forward<T>(t); 
} 

Bar process(Foo x) { 
    return Bar{x}; 
} 

E ...

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}; 
} 

L'ho su buona autorità (here) che il secondo è preferibile.

Tuttavia, non riesco a capire la spiegazione data. Penso che questo sia scavare in alcuni degli angoli più oscuri del C++.

Penso che mi manca il macchinario necessario per capire cosa sta succedendo. Qualcuno potrebbe spiegare in dettaglio? Se si sta scavando troppo, qualcuno potrebbe raccomandare una risorsa per l'apprendimento dei concetti prerequisiti necessari?

MODIFICA: Vorrei aggiungere che nel mio caso particolare la firma della funzione sta per corrispondere a uno dei typedef-s su this page. Vale a dire, ogni argomento sarà o PyObject* (con PyObject è una normale struttura C) o un tipo C di base come const char*, int, float. Quindi la mia ipotesi è che l'implementazione leggera potrebbe essere più appropriata (non sono un fan dell'eccessiva generalizzazione). Ma sono davvero interessato ad acquisire la giusta mentalità per risolvere problemi come questi.

+2

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 –

+0

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. –

+0

@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. –

risposta

2

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 :

  • il primo: quando la funzione call viene richiamata da un contesto esterno.

  • il secondo: copiare l'istanza b quando si chiama la funzione target dal corpo di call.

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 è TNoisy&& 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).

0

Non sarebbe sufficiente la prima parte della Versione 2? solo:

template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

Dato un caso di utilizzo con una conversione esistente (di costruzione per "Bar" da "Pippo"), come:

struct Foo { 
    int x; 
}; 
struct Bar { 
    int y; 
    Bar(Foo f) { 
     y = f.x; 
    } 
}; 
int main() { 

    auto b = process<Bar>(Foo()); // b will become a "Bar" 
    auto i = process<int>(1.5f); 
} 

Si è costretti a specificare il primo parametro di modello (il tipo per convertire in) in ogni caso perché il compilatore non può dedurlo. Quindi sa che tipo si aspetta e costruirà un oggetto temporaneo di tipo "Bar" perché c'è un costruttore.

+0

La domanda non è stata formulata correttamente. La domanda è stata chiarita e la tua risposta (sfortunatamente) è sbagliata. –