Penso che questi lavori ora, indipendentemente dal fatto che si utilizza il complemento a due o meno. Si prega di testare ampiamente prima di usarlo. Danno i seguenti risultati. Ogni riga fornisce un errore di asserzione (basta modificarli in eccezioni a piacere)
/* unsigned -> signed, overflow */
safe_cast<short>(UINT_MAX);
/* unsigned -> unsigned, overflow */
safe_cast<unsigned char>(ULONG_MAX);
/* signed -> unsigned, overflow */
safe_cast<unsigned long>(-1);
/* signed -> signed, overflow */
safe_cast<signed char>(INT_MAX);
/* always works (no check done) */
safe_cast<long>(INT_MAX);
// giving these assertion failures results
(type)f <= (type)is_signed<To>::v_max
f <= (To)-1
f >= 0
f >= is_signed<To>::v_min && f <= is_signed<To>::v_max
Implementazione. Innanzitutto alcune utilità per verificare la presenza di ranghi interi (i tipi con gradi più alti saranno in grado di contenere valori di tipi con grado inferiore, dato lo stesso segno. E alcuni strumenti di promozione, per essere in grado di capire un tipo comune e sicuro (questo non sarà mai produrre un tipo firmate se un tipo senza segno è coinvolto, se il tipo firmata non sarà in grado di memorizzare tutti i valori di quello non firmato).
/* ranks */
template<typename> struct int_rank;
#define RANK(T, I) template<> struct int_rank<T> \
{ static int const value = I; }
RANK(char, 1); RANK(unsigned char, 1); RANK(signed char, 1);
RANK(short, 2); RANK(unsigned short, 2);
RANK(int, 3); RANK(unsigned int, 3);
RANK(long, 4); RANK(unsigned long, 4);
#undef RANK
/* usual arith. conversions for ints (pre-condition: A, B differ) */
template<int> struct uac_at;
template<> struct uac_at<1> { typedef int type; };
template<> struct uac_at<2> { typedef unsigned int type; };
template<> struct uac_at<3> { typedef long type; };
template<> struct uac_at<4> { typedef unsigned long type; };
template<typename A, typename B>
struct uac_type {
static char (&f(int))[1];
static char (&f(unsigned int))[2];
static char (&f(long))[3];
static char (&f(unsigned long))[4];
typedef typename uac_at<sizeof f(0 ? A() : B())>::type type;
};
/* signed games */
template<typename> struct is_signed { static bool const value = false; };
#define SG(X, TT) template<> struct is_signed<X> { \
static bool const value = true; \
static X const v_min = TT##_MIN; \
static X const v_max = TT##_MAX; \
}
SG(signed char, SCHAR);
SG(short, SHRT);
SG(int, INT);
SG(long, LONG);
#undef SG
template<> struct is_signed<char> {
static bool const value = (CHAR_MIN < 0);
static char const v_min = CHAR_MIN; // just in case it's signed...
static char const v_max = CHAR_MAX;
};
I modelli di conversione fanno uso di loro, per capire per ciascun caso in cui ciò che deve essere fatto o non fatto.
template<typename To, typename From,
bool to_signed = is_signed<To>::value,
bool from_signed = is_signed<From>::value,
bool rank_fine = (int_rank<To>::value >= int_rank<From>::value)>
struct do_conv;
/* these conversions never overflow, like int -> int,
* or int -> long. */
template<typename To, typename From, bool Sign>
struct do_conv<To, From, Sign, Sign, true> {
static To call(From f) {
return (To)f;
}
};
template<typename To, typename From>
struct do_conv<To, From, false, false, false> {
static To call(From f) {
assert(f <= (To)-1);
return (To)f;
}
};
template<typename To, typename From>
struct do_conv<To, From, false, true, true> {
typedef typename uac_type<To, From>::type type;
static To call(From f) {
/* no need to check whether To's positive range will
* store From's positive range: Because the rank is
* fine, and To is unsigned.
* Fixes GCC warning "comparison is always true" */
assert(f >= 0);
return (To)f;
}
};
template<typename To, typename From>
struct do_conv<To, From, false, true, false> {
typedef typename uac_type<To, From>::type type;
static To call(From f) {
assert(f >= 0 && (type)f <= (type)(To)-1);
return (To)f;
}
};
template<typename To, typename From, bool Rank>
struct do_conv<To, From, true, false, Rank> {
typedef typename uac_type<To, From>::type type;
static To call(From f) {
assert((type)f <= (type)is_signed<To>::v_max);
return (To)f;
}
};
template<typename To, typename From>
struct do_conv<To, From, true, true, false> {
static To call(From f) {
assert(f >= is_signed<To>::v_min && f <= is_signed<To>::v_max);
return (To)f;
}
};
template<typename To, typename From>
To safe_cast(From f) { return do_conv<To, From>::call(f); }
Ma è possibile copiare il codice necessario da numeric_limits nel proprio helper di modello. Assegna tutto a uint64 (o qualunque sia la dimensione massima consentita) e fai confronti all'interno di quel tipo. –
Potrebbe funzionare, ma è necessario conoscere i termini della licenza quando si copia codice come questo. Oltre a potenzialmente violare i termini, si potrebbe inavvertitamente "infettare" il loro codice, come nel caso della GPL. Assicurati che entrambe le licenze siano compatibili prima di fare questo genere di cose. Si applica la solita dichiarazione di non responsabilità "I am not a lawyer". – Void
Quali sono i vari motivi per cui non è possibile utilizzare l'STL? – GManNickG