C++14
( C++11 との互換性のために省略)の機能constexpr
とテンプレートの使用を使用して、これが私が思いついたものです。
https://ideone.com/OSc9CI (更新版: 未署名から署名済み、短くて美しいものも受け入れるようになりました)
これは基本的std::enable_if
に type_traitsstd::is_unsigned
とstd::is_integral
. 下から上に読むのが最善です (決定木はそこから構築されるため)。
明らかに、これはほぼすべてコンパイル時に行われるため、アセンブリはかなり小さいはずです。
このソリューションは、整数および浮動小数点の元の型だけでなく、整数および浮動小数点のターゲット型も処理できます。
チェックが簡単でない場合 (つまり、データ型の境界をチェックする必要がある場合)、actual_type
値は静的にn
キャストされます。typename std::common_type<target, actual_type>::type
すべての決定is_integral
とis_unsigned
およびis_same
はコンパイル時に行われるため、実行時にこれによるオーバーヘッドはありません。チェックは、型が一般的な型にキャストされた後 (警告を回避し、オーバーフローを防ぐため) に要約されますlower_bound(target) <= value
。value <= upper_bound(target)
#include <cmath> // necessary to check for floats too
#include <cstdint> // for testing only
#include <iomanip> // for testing only
#include <iostream> // for testing only
#include <limits> // necessary to check ranges
#include <type_traits> // necessary to check type properties (very efficient, compile time!)
// the upper bound must always be checked
template <typename target_type, typename actual_type>
constexpr bool test_upper_bound(const actual_type n)
{
typedef typename std::common_type<target_type, actual_type>::type common_type;
const auto c_n = static_cast<common_type>(n);
const auto t_max = static_cast<common_type>(std::numeric_limits<target_type>::max());
return ( c_n <= t_max );
}
// the lower bound is only needed to be checked explicitely in non-trivial cases, see the next three functions
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!(std::is_unsigned<target_type>::value) && !(std::is_unsigned<actual_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
typedef typename std::common_type<target_type, actual_type>::type common_type;
const auto c_n = static_cast<common_type>(n);
const auto t_min_as_t = std::numeric_limits<target_type>::lowest();
const auto t_min = static_cast<common_type>(t_min_as_t);
return (c_n >= t_min);
}
// for signed target types where the actual type is unsigned, the lower bound is trivially satisfied.
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!(std::is_unsigned<target_type>::value) &&(std::is_unsigned<actual_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
return true;
}
// for unsigned target types, the sign of n musn't be negative
// but that's not an issue with unsigned actual_type
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<std::is_integral<target_type>::value &&
std::is_unsigned<target_type>::value &&
std::is_integral<actual_type>::value &&
std::is_unsigned<actual_type>::value, bool>::type
test_lower_bound(const actual_type)
{
return true;
}
// for unsigned target types, the sign of n musn't be negative
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<std::is_integral<target_type>::value &&
std::is_unsigned<target_type>::value &&
(!std::is_integral<actual_type>::value ||
!std::is_unsigned<actual_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
return ( n >= 0 );
}
// value may be integral if the target type is non-integral
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!std::is_integral<target_type>::value, bool>::type
test_integrality(const actual_type)
{
return true;
}
// value must be integral if the target type is integral
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<std::is_integral<target_type>::value, bool>::type
test_integrality(const actual_type n)
{
return ( (std::abs(n - std::floor(n)) < 1e-8) || (std::abs(n - std::ceil(n)) < 1e-8) );
}
// perform check only if non-trivial
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!std::is_same<target_type, actual_type>::value, bool>::type
CanTypeFitValue(const actual_type n)
{
return test_upper_bound<target_type>(n) &&
test_lower_bound<target_type>(n) &&
test_integrality<target_type>(n);
}
// trivial case: actual_type == target_type
template <typename actual_type>
constexpr bool CanTypeFitValue(const actual_type)
{
return true;
}
int main()
{
int ns[] = {6, 1203032847, 2394857, -13423, 9324, -192992929};
for ( const auto n : ns )
{
std::cout << std::setw(10) << n << "\t";
std::cout << " " << CanTypeFitValue<int8_t>(n);
std::cout << " " << CanTypeFitValue<uint8_t>(n);
std::cout << " " << CanTypeFitValue<int16_t>(n);
std::cout << " " << CanTypeFitValue<uint16_t>(n);
std::cout << " " << CanTypeFitValue<int32_t>(n);
std::cout << " " << CanTypeFitValue<uint32_t>(n);
std::cout << " " << CanTypeFitValue<int64_t>(n);
std::cout << " " << CanTypeFitValue<uint64_t>(n);
std::cout << " " << CanTypeFitValue<float>(n);
std::cout << " " << CanTypeFitValue<double>(n);
std::cout << "\n";
}
std::cout << "\n";
unsigned long long uss[] = {6, 1201146189143ull, 2397, 23};
for ( const auto n : uss )
{
std::cout << std::setw(10) << n << "\t";
std::cout << " " << CanTypeFitValue<int8_t>(n);
std::cout << " " << CanTypeFitValue<uint8_t>(n);
std::cout << " " << CanTypeFitValue<int16_t>(n);
std::cout << " " << CanTypeFitValue<uint16_t>(n);
std::cout << " " << CanTypeFitValue<int32_t>(n);
std::cout << " " << CanTypeFitValue<uint32_t>(n);
std::cout << " " << CanTypeFitValue<int64_t>(n);
std::cout << " " << CanTypeFitValue<uint64_t>(n);
std::cout << " " << CanTypeFitValue<float>(n);
std::cout << " " << CanTypeFitValue<double>(n);
std::cout << "\n";
}
std::cout << "\n";
float fs[] = {0.0, 0.5, -0.5, 1.0, -1.0, 1e10, -1e10};
for ( const auto f : fs )
{
std::cout << std::setw(10) << f << "\t";
std::cout << " " << CanTypeFitValue<int8_t>(f);
std::cout << " " << CanTypeFitValue<uint8_t>(f);
std::cout << " " << CanTypeFitValue<int16_t>(f);
std::cout << " " << CanTypeFitValue<uint16_t>(f);
std::cout << " " << CanTypeFitValue<int32_t>(f);
std::cout << " " << CanTypeFitValue<uint32_t>(f);
std::cout << " " << CanTypeFitValue<int64_t>(f);
std::cout << " " << CanTypeFitValue<uint64_t>(f);
std::cout << " " << CanTypeFitValue<float>(f);
std::cout << " " << CanTypeFitValue<double>(f);
std::cout << "\n";
}
}
この (新しい) バージョンは、(コンパイル時に!) チェックが必要かどうか (上限、下限、および完全性に関して) を迅速に決定し、正しいバージョンを使用します (署名されていない型との愚かな >= 0 比較に関する警告を避けるため) (コンパイル時も)時間)。たとえば、ターゲットが float の場合は完全性をチェックする必要はなく、両方の型が unsigned の場合は下限をチェックする必要はありません。
最も明白な最適化 (等しい型を持つ) は、 で行われstd::is_same
ます。
このアプローチは、特殊なテンプレートを使用して使用定義型に拡張することもできます。などのチェックstd::is_integral
は、それらのタイプに対して否定的になります。
ここで、または -S を指定して g++ を呼び出すことにより、アセンブラーの出力がかなり小さいことを確認できます (float の明白なケースを除く) 。