2016-03-31 23 views
5

Mi piacerebbe generalizzare operatori bit a bit in C++ senza pensare che la struttura sottostante sia una matrice.Metaprogrammazione per l'ottimizzazione dell'algoritmo di archiviazione/runtime, C++

Come esempio ... se voglio rappresentare 86 bit vorrei usare una struttura di struttura/classe come:

typedef struct { 
uint64_t x[1]; 
uint16_t y[1]; 
uint8_t z[1]; 
} sampleStruct; 

Invece se mi sarebbe piaciuto di destinare 160 bit userei una struttura come:

typedef struct { 
uint64_t x[2]; 
uint32_t y[1]; 
} sampleStruct; 

Immagino che una soluzione banale, ma non ottimale, per lo storage sia assumere che tutti i blocchi siano uniformi e allocare il minimo di quelli st copre le dimensioni che sto implementando, tuttavia anche per una questione di esercizio preferisco il modo in cui ho esposto.

A me suona chiaro che dovrei usare metaprogrammazione per risolvere il problema, quindi devo definire correttamente

template <int I> 
typedef sampleStruct { 
    //something 
} 

Comunque io non sono un grande esperto di C++ template metaprogrammazione quindi vorrei capire quale sarebbe il modo migliore per attuare i diversi tipi di campione struct varing I. so come decidere la migliore "copertura" per la mia lunghezza sarebbe qualcosa di simile:

N64 = I/64; 
RemN = I%64; 
if(0 < RemN <= 8) { 
    add uint8_t var; 
} else if (8 < RemN <= 16) { 
    add uint16_t var; 
} else if (16 < RemN <= 24) { 
    add uint16_t var; 
    add uint8_t var; 
} else { 
    //Similarly handle the other cases from 24 < RemN < 64 
} 

Cosa posso fare per ottenere ciò che Voglio fare?

Immagino anche che i blocchi arrigrati correttamente consentirebbero di ottenere prestazioni leggermente migliori rispetto ad altre possibili implementazioni.

Sperando che sia abbastanza chiaro ... (Si consideri C++ 11 o più versioni recenti).

+7

Suoni come si desidera ['std :: bitset'] (http://en.cppreference.com/w/cpp/utility/bitset). – Daniel

+0

Non sapevo che esistesse, funziona nel modo in cui ho descritto? (lo stoccaggio intendo). – user8469759

+0

c'è un discorso su questo: https://www.youtube.com/watch?v=ea5DiCg8HOY –

risposta

0

È possibile, ma è necessaria una buona dose di digitazione. Il problema è che C++ non fornisce un modo per meta-programmazione omettere un membro di dati (si veda ad esempio Conditional Inclusion/Exclusion of Data Members Inside Class Templates) in modo da avere a specializzarsi sulla sua presenza o assenza:

template<int N64, bool P32, bool P16, bool P8> 
struct sampleStructImpl; 

template<int I> 
using sampleStruct = sampleStructImpl<I/64, (I%64 >= 32), (I%32 >= 16), (I%16 >= 8)>; 

Le varie specializzazioni parziali (8 in totale) sarebbe simile al seguente:

template<int N64> 
struct sampleStructImpl<N64, true, true, true> 
{ 
    std::uint64_t x[N64]; 
    std::uint32_t y; 
    std::uint16_t z; 
    std::uint8_t w; 
}; 

template<int N64> 
struct sampleStructImpl<N64, true, true, false> 
{ 
    std::uint64_t x[N64]; 
    std::uint32_t y; 
    std::uint16_t z; 
    // omit std::uint8_t w; 
}; 

// 6 more partial specializations... 

Inoltre, dal momento che gli array di lunghezza zero sono illegali, se si vuole essere in grado di consentire valori di I inferiore a 64 si dovrà specializzarsi su N64 essere pari a zero:

template<> 
struct sampleStructImpl<0, true, true, true> 
{ 
    // omit std::uint64_t x[0]; 
    std::uint32_t y; 
    std::uint16_t z; 
    std::uint8_t w; 
}; 

// 7 more specializations... 

Sarebbe molto più semplice utilizzare std::array<std::uint8_t, (I + 7)/8>, possibilmente con un modificatore alignas a 64 bit allinearlo.

0

Non sarebbe più facile da usare solo una serie di uint8_t, ad esempio:

template <int I> 
struct sampleStruct { 
    std::uint8_t v[(I % 8)? (I/8) + 1 : (I/8)]; 
}; 

Come lei ha ricordato bit, sto supponendo che non accedere ai singoli membri x,y,z, (è non è chiaro dalla domanda in che modo accedere ai bit sottostanti ..)

E 'possibile fare ciò che si desidera così, ma è necessario utilizzare l'ereditarietà, come segue:

template <int I> 
struct largeBlock { 
    std::uint64_t x[I]; 
}; 
template <> 
struct largeBlock<0> { 
}; 

template <int I> 
struct mediumBlock { 
    std::uint16_t y[I]; 
}; 
template <> 
struct mediumBlock<0> { 
}; 

template <int I> 
struct smallBlock { 
    std::uint8_t z[(I/8) + ((I % 8) ? 1 : 0) ]; 
}; 
template <> 
struct smallBlock<0> { 
}; 

template <int I> 
struct sampleStruct : largeBlock<I/64>, mediumBlock<(I % 64)/16>, smallBlock<(I % 16)> { 

}; 

Ora le operazioni devono essere attuate in termini di chiamate verso gli oggetti di base ...

+0

Permette decisamente di ottenere ciò che voglio fare, in termini di spazio di archiviazione sarebbe il minimo richiesto. Tuttavia ci sarebbe una differenza computazionale se si desidera implementare un operatore di turno come esempio. – user8469759

+0

L'approccio sopra è anche efficiente per la memorizzazione (che è quello che cercavi), con il tuo setup e padding, le tue strutture saranno piuttosto grandi (per esempio 16 byte per 86 bit contro 11) - quindi per l'ottimizzazione dello storage quanto sopra è difficile battere .. Se si riformula la domanda per considerare il costo dell'operazione di runtime, allora è anche possibile .. – Nim

+0

Perché la mia implementazione per 86 bit userebbe 16 byte? È 64 + 16 + 8, cioè 8 + 2 + 1 byte = 11 byte. Stavo pensando a strutture molto grandi, Se prendi un caso limite, diciamo 1024 bit ho bisogno di 16 variabili uint64_t, contro 128 uint8_t, io faccio uno spostamento a destra di 1 bit vorrei ciclizzare su 16 variabili uint64_t contro 128 uint8_t, penso ogni cosa che fai su una porzione di 8 bit sarebbe la stessa in 64 bit in questo caso. Un altro caso banale ... diciamo che vuoi allocare 8 byte, nel tuo caso useresti 4 byte per farlo, ho ragione? Sarebbe meglio o equivalente? – user8469759