20

プリミティブ データ型が潜在的に異なるプリミティブ データ型の値に適合するかどうかをチェックするテンプレート化された関数を作成することは可能ですか? とりあえずスコープを整数型に限定しましょう。

より正確には、コンパイラの警告 (ブール式は常に true/false、符号付き/符号なしの比較、未使用の変数) を取得せずに、コンパイラの警告チェックを無効にすることなく、「すべてに適合する」テンプレート化された関数を作成することは可能ですか? また、関数は実行時のチェックを可能な限り制限する必要があります (すべての些細なケースはコンパイル時に除外する必要があります)。可能であれば、C++11 などの拡張機能の使用は避けたいと思います (「古い」C++ の「迅速な」代替が存在しない限り)。

注: 「値」はコンパイル時には不明であり、その型のみが不明です。

予想される動作の例:

int main(int argc, char** argv) {
    for (int i = 1; i < argc; i++) {
        const int value = atoi(argv[i]);
        std::cout << value << ": ";
        std::cout << CanTypeFitValue<int8_t>(value) << " ";
        std::cout << CanTypeFitValue<uint8_t>(value) << " ";
        std::cout << CanTypeFitValue<int16_t>(value) << " ";
        std::cout << CanTypeFitValue<uint16_t>(value) << " ";
        std::cout << CanTypeFitValue<int32_t>(value) << " ";
        std::cout << CanTypeFitValue<uint32_t>(value) << " ";
        std::cout << CanTypeFitValue<int64_t>(value) << " ";
        std::cout << CanTypeFitValue<uint64_t>(value) << std::endl;
        }
    
}

出力:

./a.out 6 1203032847 2394857 -13423 9324 -192992929

6: 1 1 1 1 1 1 1 1

1203032847: 0 0 0 0 1 1 1 1

2394857: 0 0 0 0 1 1 1 1

-13423: 0 0 1 0 1 0 1 0

9324: 0 0 1 1 1 1 1 1

-192992929: 0 0 0 0 1 0 1 0

ここまたはここでコードをテストします。

ここで生成されたアセンブリを確認してください。

この質問はこの投稿に触発されました

4

7 に答える 7

9

C++14( C++11 との互換性のために省略)の機能constexprとテンプレートの使用を使用して、これが私が思いついたものです。

https://ideone.com/OSc9CI (更新版: 未署名から署名済み、短くて美しいものも受け入れるようになりました)

これは基本的std::enable_ifに type_traitsstd::is_unsignedstd::is_integral. 下から上に読むのが最善です (決定木はそこから構築されるため)。

明らかに、これはほぼすべてコンパイル時に行われるため、アセンブリはかなり小さいはずです。

このソリューションは、整数および浮動小数点の元の型だけでなく、整数および浮動小数点のターゲット型も処理できます。

チェックが簡単でない場合 (つまり、データ型の境界をチェックする必要がある場合)、actual_type値は静的にnキャストされます。typename std::common_type<target, actual_type>::type

すべての決定is_integralis_unsignedおよびis_sameはコンパイル時に行われるため、実行時にこれによるオーバーヘッドはありません。チェックは、型が一般的な型にキャストされた後 (警告を回避し、オーバーフローを防ぐため) に要約されますlower_bound(target) <= valuevalue <= 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 の明白なケースを除く) 。

于 2013-06-27T11:39:31.407 に答える
5

そうです

template <typename T, typename U>
constexpr bool CanTypeFitValue(const U value)
{return ((value>U(0))==(T(value)>T(0))) && U(T(value))==value;}

//      (         part1         ) && (      part2      )

基本的に、これには 2 つの部分があります。最初の部分は、符号の変更が発生した場合 (またはその逆にキャストunsignedした場合)、符号情報が失われないことを確認します。2 番目の部分は、が a にキャストされてから戻されたかどうか、その値が保持されていること、およびビットが保持されていないことを確認します。失われました。signedvalueT

参考までに、値が維持されているかどうかを判断するにはこれで十分かどうかはわかりませんが、失敗するプリミティブのケースをすぐに考えることはできません。T私の答えとケーシーの答えはどちらも、との間の両方の方法で変換演算子を提供する限り、ユーザー定義の数値のような型で機能するはずUです。

これは、質問に投稿したテストに合格したことの証明です

于 2013-06-20T23:40:53.173 に答える
1

numeric_limitsを使用して解決策を提案します

#include <limits>
using std::numeric_limits;

template <typename T, typename U>
    bool CanTypeFitValue(const U value) {
        if (numeric_limits<T>::is_signed == numeric_limits<U>::is_signed) {
            if (numeric_limits<T>::digits >= numeric_limits<U>::digits)
                return true;
            else
                return (static_cast<U>(numeric_limits<T>::min() ) <= value && static_cast<U>(numeric_limits<T>::max() ) >= value);
        }
        else {
            if (numeric_limits<T>::is_signed) {
                if (numeric_limits<T>::digits > numeric_limits<U>::digits) //Not >= in this case!
                    return true;
                else
                    return (static_cast<U>(numeric_limits<T>::max() ) >= value);
            }
            else ///U is signed, T is not
                if (value < static_cast<U> (0) )
                    return false;
                else
                    if (numeric_limits<T>::digits >= numeric_limits<U>::digits)
                        return true;
                    else
                        return (static_cast<U>(numeric_limits<T>::max() ) >= value);
        }
    }

ここでテストしました(atoiを使用して申し訳ありません:))。

于 2013-06-20T21:37:34.190 に答える
-1

最も明示的な方法は、おそらく SFINAE と各タイプの関数を使用することです。このようなもの:

#include <limits>


template <typename T>
bool CanTypeFitValue(int) {
    return false;
}

template <typename T>
bool CanSignedNumericTypeFitValue(int value) {
    return (value >= std::numeric_limits<T>::min() && 
            value <= std::numeric_limits<T>::max());
}

template <typename T>
bool CanUnsignedNumericTypeFitValue(int value) {
    return (value >= 0 && 
            static_cast<unsigned>(value) <= std::numeric_limits<T>::max());
}

template <> bool CanTypeFitValue<int8_t>(int value) { 
    return CanSignedNumericTypeFitValue<int8_t>(value); 
}
template <> bool CanTypeFitValue<uint8_t>(int value) {
    return CanUnsignedNumericTypeFitValue<uint8_t>(value); 
}
// .....
//template <> bool CanTypeFitValue<SomeUserClass * > { 
//    return impl_details(value);
//};

STL/Boostなどでもよく使われます。

主なアイデアは、ユーザー定義型とともに関数を定義できることです。

于 2013-06-24T21:04:30.457 に答える