2012-04-15 19 views
7

I modelli C++ sono generalmente assimilati ai creatori di bloat e l'idea di Shim si occupa esattamente di questo: rendere il modello solo un involucro sottile su una funzione regolare. È davvero un ottimo modo per ridurre il peso.Un modo più succinto di utilizzare gli shim nei modelli variadici?

Per esempio, usiamo un semplice spessore:

// 
// Shim interface 
// 
struct Interface { 
    virtual void print(std::ostream& out) const = 0; 
}; // struct Interface 

std::ostream& operator<<(std::ostream& out, Interface const& i) { 
    i.print(out); 
    return out; 
} 

template <typename T> 
struct IT: public Interface { 
    IT(T const& t): _t(t) {} 
    virtual void print(std::ostream& out) const { out << _t; } 
    T const& _t; 
}; 

template <typename T> 
IT<T> shim(T const& t) { return IT<T>(t); } 

Ora, posso usarlo in questo modo:

void print_impl(Interface const& t); 

template <typename T> 
void print(T const& t) { print_impl(shim(t)); } 

E non importa quanto print_impl è implementata, print rimane molto leggero e dovrebbe essere in linea. Vai tranquillo.


C++ 11 introduce tuttavia variadic modelli. L'impulso tipico è quindi quello di reimplementare tutti i C-variadics non sicuri con i modelli variadici di C++ 11, anche Wikipedia lo suggerisce con lo a printf implementation.

Sfortunatamente, l'implementazione di Wikipedia non tratta argomenti posizionali: il tipo che consente di specificare stampare il terzo parametro di là, ecc ... Sarebbe facile, se solo avessimo una funzione con questo prototipo:

void printf_impl(char const* format, Interface const* array, size_t size); 

o simile.

Ora, come possiamo ponte dalla interfaccia originale:

template <typename... T> 
void printf(char const* format, T const&... t); 

alla firma di cui sopra?

Una difficoltà con gli spessori è che si basano sul legame al comportamento const-ref per estendere la durata di vita del involucro temporaneo creato quel tanto che basta, senza dover allocare dinamicamente la memoria (che avrebbero non essere a buon mercato, se hanno fatto).

Sembra difficile però ottenere quel binding + la trasformazione dell'array in un solo passaggio. Soprattutto perché non sono consentiti matrici di riferimenti (e puntatori ai riferimenti) nella lingua.


Ho un inizio di una soluzione, per chi fosse interessato:

// 
// printf (or it could be!) 
// 
void printf_impl(char const*, Interface const** array, size_t size) { 
    for (size_t i = 0; i != size; ++i) { std::cout << *(array[i]); } 
    std::cout << "\n"; 
} 

template <typename... T> 
void printf_bridge(char const* format, T const&... t) { 
    Interface const* array[sizeof...(t)] = { (&t)... }; 
    printf_impl(format, array, sizeof...(t)); 
} 

template <typename... T> 
void printf(char const* format, T const&... t) { 
    printf_bridge(format, ((Interface const&)shim(t))...); 
} 

tuttavia si nota l'introduzione di una fase complementare, che è un po 'fastidioso. Ancora, it appears to work.

Sarei molto grato se qualcuno avesse una migliore implementazione da proporre.


@Potatoswatter suggerito usando liste di inizializzazione, che helps a bit (nessuna fascia-per lì).

void printf_impl(char const*, std::initializer_list<Interface const*> array) { 
    for (Interface const* e: list) { std::cout << *e; } 
    std::cout << "\n"; 
} 

template <typename... T> 
void printf_bridge(char const* format, T const&... t) { 
    printf_impl(format, {(&t)...}); 
} 

Tuttavia, non risolve il problema della funzione intermedia.

+2

Oh no, sbuffo! Alcuni KB in più nel mio eseguibile sono la fine del mio mondo. – Puppy

+3

@DeadMG questo tipo di cose può davvero risparmiare megabyte. – Potatoswatter

+0

Non penso che la variabile membro 'T const & _t;' in 'IT' estende la durata del temporaneo restituito dalla funzione' shim() '. Quando il costruttore ritorna, '_t' si riferisce all'oggetto destructed. – Nawaz

risposta

3

Rendere le cerniere leggere eliminando la parametrizzazione del tipo. Il tuo shim potenzialmente istanzia qualcosa di pesante con l'espressione out << _t, quindi potrebbe non essere un buon esempio.

C varargs gestisce il problema colando implicitamente tutto su intptr_t. Se si desidera solo replicare la funzionalità C printf, è possibile fare lo stesso con reinterpret_cast e uno initializer_list.

template <typename... T> 
void printf(char const* format, T const&... t) { 
    printf_impl(format, { reinterpret_cast<std::intptr_t>(t) ... }); 
} 

Questo è ovviamente non ottimale, ma spessori sono intrinsecamente limitato. Si potrebbe fare qualcos'altro con i tipi polimorfi nel initializer_list se si desidera.

In ogni caso, questo è esattamente il significato di initializer_list. Può essere costruito solo da una lista di init rinforzata, rendendo le sue dimensioni una costante in fase di compilazione. Ma la dimensione può essere riletta solo come costante di runtime. Quindi il suo unico uso pratico è quello di canalizzare i modelli che differiscono solo nella lunghezza dell'elenco per un'implementazione comune di lunghezza variabile.

A questo si aggiunge la semantica durata di initializer_list argomenti - gli oggetti vengono creati in un array contiguo sullo stack e muoiono quando l'istruzione funzione di chiamata termina - e initializer_list assomiglia molto <varargs>! (Edit: o la vostra soluzione, che ora ho effettivamente andato indietro e leggere: VP)

Edit: Dato che i contenitori non possono memorizzare direttamente oggetti polimorfici, e puntatori intelligenti non sono appropriati per gli oggetti di argomento temporanei, attuazione il polimorfismo richiederebbe il puntamento ai temporari. Brutto, ma legale a causa della durata garantita per gli oggetti temporanei:

template <typename... T> 
void printf(char const* format, T const&... t) { 
    printf_impl(format, std::initializer_list< Interface const * > 
     { & static_cast< Interface const & >(shim(t))... }); 
} 
+0

La 'initializer_list' è carina, sfortunatamente non supporta riferimenti, a causa di typedef interni. Ancora aiuta un po 'a rendere il codice più leggero. Pensi che '{(& (Interface const &) shim (t)) ...}' sarebbe valido in 'printf'? errori di ideone su questo: "errore: conversione da' 'a tipo non scalare' std :: initializer_list 'richiesto" –

+0

@MatthieuM. Non supporta i riferimenti perché è un contenitore e costruisce un array. Non penso che ci sia una soluzione al problema che non coinvolga un array (o un altro contenitore), perché quale altra interfaccia accetterebbe l'implementazione non-template? – Potatoswatter

+0

@MatthieuM. Beh, probabilmente si tratta di un bug in G ++, perché quella lista di inizializzazione racchiusa tra parentesi * è * convertibile in quella specializzazione 'initializer_list'. Hmm, non avevo pensato al problema con polimorfismo e contenitori. Sì, anche se questo dovrebbe essere 'static_cast', immagino che potrebbe essere la soluzione migliore ... – Potatoswatter

0

Se è possibile utilizzare omogeneo (stessa dimensione e l'allineamento in memoria) tipi diano un'occhiata at that:

// thin template layer over regular class/methods 
template< typename T, typename... Contracts> 
inline void Container::bindSingleAs(){ 

isMultiBase< T, Contracts...>(); //compile time test 

    priv::TypeInfoP   types[ sizeof...(Contracts)] 
          { &typeid(Contracts)... }; 

    priv::SharedUpcastSignature upcasts[ sizeof...(Contracts)] 
          { &priv::shared_upcast< T, Contracts>... }; 

    // dispatch over non-template method. 
    container->bindSingleAs(&typeid(T), types, upcasts, sizeof...( Contracts)); 
} 

Ora, dopo la modifica a causa di commenti, penso che ci siano 2 requisiti in conflitto.

  1. Vuoi un parametro array
  2. desidera la copia sovraccarico

Se la funzione printf_impl richiede un array come parametro, allora questo significa che gli elementi dell'array devono avere la stessa disposizione in memoria (che significa che se 1 elemento è 64 byte che forza tutti gli altri elementi da 64 byte allineati anche se sono 1 byte ..) quindi è necessaria una copia o almeno la copia in un puntatore a una posizione fissa, quindi è sicuramente NON POSSIBILE IBLE fa ciò che l'OP voleva.

Possiamo ancora costruire tale matrice, ma siamo costretti:

  1. non vogliamo la copia a tutti, allora dovremmo dichiarare in modo statico il tipo di matrice, questa forza noi per costruire un terzo tipo.

    auto Array = MakeArray(/* values*/);

    printf(Array);

  2. Accettiamo la copia, in modo da costruire la matrice all'interno della funzione, dal momento che i valori non sono noti possiamo nascondere matrice da utente, ma dobbiamo copiare i parametri per locazioni di memoria fissa , tuttavia abbiamo ancora l'array nascosto sotto il cofano.

  3. Assegnazione heap, che consente di passare i parametri in un array molto compatto, tuttavia i parametri devono risiedere altrove e l'allocazione dell'heap può essere costosa.

La prima soluzione è quella di accettare un ulteriore complessità nella codifica creando un staticamente tipizzati elementi wich di matrice possono essere indirizzate (tutti allineati a grande elemento), tuttavia, che non è ottimale in quanto che aumentano le dimensioni del wich dell'oggetto può colpire comunque prestazioni (se quell'array vive anche dopo la chiamata di funzione)

La seconda soluzione nasconde la complessità dietro l'interfaccia di modello, tuttavia non può evitare il costo delle prestazioni di copiare temporaneamente i valori su un array identico a quello del prima soluzione.

Quindi, non è possibile farlo, mi dispiace. L'altra risposta è compresa tra il numero 2 e 3. Tutte le altre risposte possibili rientrerebbero in una delle 3 categorie.

+0

Ci scusiamo per la confusione Shim non è il nome di una persona, è una normale [parola inglese] (http://www.merriam-webster.com/dictionary/shim). La data della domanda del 2012 dovrebbe dirvi che l'idea dello shim è molto più antica del vostro repository, ma per essere onesti è completamente irrilevante. A nessuno importa chi ha inventato per primo (a meno che non siano coinvolti i brevetti) il mondo è pieno di ri-scoperte. Ti consiglierei di tagliare la tua risposta alle parti rilevanti ... –

+0

Ah :) grazie, ho rifilato e migliorato un po ', alcune parti non chiare o è tutto buono per te? – GameDeveloper

+0

... tuttavia prima la risposta dovrebbe essere riorientata. La domanda è specificatamente come passare da 'template void printf (char const * format, T const & ... args)' a 'void print_impl (char const *, Interface const *, size_t)' oppure 'void print_impl (char const *, std :: initializer_list )' senza un passaggio intermedio (che è difficile a causa dell'inclusione dello shim), tuttavia questa risposta non ha uno shim da cui partire, quindi a meno che non spieghi perché può funzionare senza uno shim (cioè, questa classe 'Interface') o che lo shim non è un problema, non sta affrontando la domanda. –

Problemi correlati