Come scrivere una funzione constexpr
per scambiare l'endianess di un intero, senza fare affidamento sulle estensioni del compilatore e puoi dare un esempio su come farlo?Come scrivere la funzione di scambio constexpr per cambiare l'endianess di un intero?
risposta
Sì, è piuttosto semplice; ecco un'implementazione ricorsiva (C++ 11-compatibile) (solo tipi interi senza segno):
#include <climits>
#include <cstdint>
#include <type_traits>
template<class T>
constexpr typename std::enable_if<std::is_unsigned<T>::value, T>::type
bswap(T i, T j = 0u, std::size_t n = 0u) {
return n == sizeof(T) ? j :
bswap<T>(i >> CHAR_BIT, (j << CHAR_BIT) | (i & (T)(unsigned char)(-1)), n + 1);
}
Qui sto usando j
come l'accumulatore e n
come il contatore di ciclo (indicizzazione bytes) .
Se si dispone di un compilatore di supporto C++17 fold expressions, è possibile scrivere qualcosa che si espande fuori in esattamente quello che ci si scrive a mano:
template<class T, std::size_t... N>
constexpr T bswap_impl(T i, std::index_sequence<N...>) {
return ((((i >> (N * CHAR_BIT)) & (T)(unsigned char)(-1)) <<
((sizeof(T) - 1 - N) * CHAR_BIT)) | ...);
}; // ^~~~~ fold expression
template<class T, class U = typename std::make_unsigned<T>::type>
constexpr U bswap(T i) {
return bswap_impl<U>(i, std::make_index_sequence<sizeof(T)>{});
}
Il vantaggio di questa forma è che perché non usa loop o ricorsione, sei praticamente garantito per ottenere un output di assemblaggio ottimale - su x86-64, clang arriva anche a work out to use the bswap
instruction.
Ispirato da ecatmur suggerisco la seguente soluzione, che ha prestazioni potenzialmente migliori quando bswap non viene rilevato dal compilatore (O (log (n)) vs O (N)). Dato che N è di solito < = 8 questo è probabilmente irrilevante, ancora:
template <typename T>
typename std::enable_if<std::is_unsigned<T>::value,T>::type
constexpr alternating_bitmask(const size_t step){
T mask(0);
for (size_t i=0;i<digits<T>();i+=2*step){
mask|=(~T(0)>>(digits<T>()-step))<<i;
}
return mask;
}
template <typename T>
typename std::enable_if<std::is_unsigned<T>::value,T>::type
constexpr bswap(T n){
for (size_t i=digits<unsigned char>();i<digits<T>();i*=2){
n = ((n&(~(alternating_bitmask<T>(i))))>>i)|
((n&((alternating_bitmask<T>(i))))<<i);
}
return n;
}
Poiché questa forma è più complessa di quanto la soluzione di ecatmur il compilatore ha un difficile ottimizzazione del lavoro, ma clang trova ancora che intendiamo bswap.
Questa soluzione ha in realtà una complessità temporale di Θ (N), poiché il ciclo interno (senza contare le ottimizzazioni) ha una complessità ammortizzata di Θ (N/log N) (per iterazione del ciclo esterno). Per ottenere l'effettivo Θ (log N), le maschere di bit dovrebbero essere memoizzate, ad es. precompilato in un array. –
@ArneVogel questo è vero, ho appena assunto che le maschere di bit saranno costanti di tempo di compilazione in quanto la funzione che le genera è un constexpr. – Lykos
Qual è la "endianità di un intero"? Qual è il endianness di 15? –
@KerrekSB Qualunque cosa sia. Non ho fatto quella domanda. La mia domanda è come scambiare big-endian con little-endian e viceversa. – user1095108
@KerrekSB: Nel contesto del C++ (e della maggior parte della programmazione in generale), quando si dice intero, di solito si riferiscono a un oggetto intero. Cioè, una regione in memoria utilizzata per memorizzare i dati interi, di solito uno dei tipi interi fondamentali (char, short, int, long e long long, insieme alle loro varianti senza segno). Non hai mai incontrato questo utilizzo? –