C++20 との積分の安全な比較

符号付き整数と符号なし整数を比較すると、期待した結果が得られない場合があります。 6 つの std::cmp_ のおかげで * 関数、C++20 には解決策があります。

おそらく、C++ コア ガイドラインの「ES.100 符号付き演算と符号なし演算を混在させないでください」というルールを覚えているでしょう。これについては、以前の記事「算術規則」で少し書きました。今日は、この問題をさらに掘り下げて、符号付き整数と符号なし整数を比較したいと思います。

安全でない比較から始めましょう。

安全でない積分の比較

もちろん、プログラム名 unsafeComparison.cpp には理由があります .

// unsafeComparison.cpp

#include <iostream>

int main() {

 std::cout << std::endl;

 std::cout << std::boolalpha;

 int x = -3; // (1)
 unsigned int y = 7; // (2)

 std::cout << "-3 < 7: " << (x < y) << std::endl;
 std::cout << "-3 <= 7: " << (x <= y) << std::endl;
 std::cout << "-3 > 7: " << (x > y) << std::endl;
 std::cout << "-3 => 7: " << (x >= y) << std::endl;

 std::cout << std::endl;
 
}

プログラムを実行すると、出力が期待どおりにならない場合があります。

プログラムの出力を読むと、-3 が 7 より大きくなければならないことがわかります。おそらく理由はわかっているはずです。 signed x を比較しました (行 (1)) unsigned y (2行目))。ボンネットの下で何が起こっているのですか?次のプログラムが答えを提供します。

// unsafeComparison2.cpp

int main() {
 int x = -3;
 unsigned int y = 7;

 bool val = x < y; // (1)
 static_assert(static_cast<unsigned int>(-3) == 4'294'967'293);
}

この例では、小なり演算子に注目しています。 C++ Insights は次の出力を提供します:

何が起こっているかは次のとおりです:

<オール>
  • コンパイラは式 x < y を変換します (1 行目) static_cast<unsigned int>(x) < y に .特に、signed x unsigned int に変換されます。
  • 変換により、 -3 4'294'967'293 になります。
  • 4'294'967'293 (-3) モジュロ (2 の 32 乗) に等しい
  • 32 は unsigned int のビット数です。 C++ インサイトについて。

  • C++20 のおかげで、積分を安全に比較できます。

    積分の安全な比較

    C++20 は、積分の 6 つの比較関数をサポートしています:

    6 つの比較関数のおかげで、以前のプログラム unsafeComparison.cpp を簡単に変換できます。 プログラム safeComparison.cpp. に 新しい比較関数にはヘッダー <utility が必要です>.

    // safeComparison.cpp
    
    #include <iostream>
    #include <utility>
    
    int main() {
    
     std::cout << std::endl;
    
     std::cout << std::boolalpha;
    
     int x = -3;
     unsigned int y = 7;
    
     std::cout << "3 == 7: " << std::cmp_equal(x, y) << std::endl;
     std::cout << "3 != 7: " << std::cmp_not_equal(x, y) << std::endl;
     std::cout << "-3 < 7: " << std::cmp_less(x, y) << std::endl;
     std::cout << "-3 <= 7: " << std::cmp_less_equal(x, y) << std::endl;
     std::cout << "-3 > 7: " << std::cmp_greater(x, y) << std::endl;
     std::cout << "-3 => 7: " << std::cmp_greater_equal(x, y) << std::endl;
     
     std::cout << std::endl;
     
    }
    

    このプログラムでは、等しい演算子と等しくない演算子も使用しました。

    GCC 10 のおかげで、期待される結果は次のようになります:

    非整数値で比較関数を呼び出すと、コンパイル時エラーが発生します。

    // safeComparison2.cpp
    
    #include <iostream>
    #include <utility>
    
    int main() {
    
     double x = -3.5; // (1)
     unsigned int y = 7; // (2)
    
     std::cout << "-3.5 < 7: " << std::cmp_less(x, y) << std::endl;
    
    }
    

    double を比較しようとしています (行 (1)) と unsigned int (行 (2)) は、GCC 10 コンパイラに長いエラー メッセージを表示します。エラーメッセージの重要な行は次のとおりです:

    内部型特性 __is_standard_integer が失敗しました。私はそれが何を意味するのか興味があり、GitHub の GCC type-traits 実装で調べました。ヘッダーの関連行は次のとおりです type-traits:

    // Check if a type is one of the signed or unsigned integer types.
     template<typename _Tp>
     using __is_standard_integer
     = __or_<__is_signed_integer<_Tp>, __is_unsigned_integer<_Tp>>;
    
    // Check if a type is one of the signed integer types.
     template<typename _Tp>
     using __is_signed_integer = __is_one_of<__remove_cv_t<_Tp>,
     signed char, signed short, signed int, signed long,
     signed long long
    
    // Check if a type is one of the unsigned integer types.
     template<typename _Tp>
     using __is_unsigned_integer = __is_one_of<__remove_cv_t<_Tp>,
     unsigned char, unsigned short, unsigned int, unsigned long,
     unsigned long long
    

    __remove_cv_t const を削除する GCC の内部関数です。 または volatile タイプから。

    たぶん、double を比較するとどうなるか興味があるかもしれません。 そして unsigned int

    これが修正されたプログラム safeComparison2.cpp. です

    // classicalComparison.cpp
    
    int main() {
    
     double x = -3.5; 
     unsigned int y = 7; 
    
     auto res = x < y; // true
     
    }
    

    できます。重要な unsigned int double に昇格された浮動小数点 . C++ Insights は真実を示します:

    非常に多くの比較を行った後、C++20 で使用できる新しい数学定数についてこの投稿を終了したいと思います。

    数学定数

    まず、定数にはヘッダー <numbers> が必要です および名前空間 std::numbers .次の表は、最初の概要を示しています。

    プログラム mathematicConstants.cpp 数学定数を適用します。

    // mathematicConstants.cpp
    
    #include <iomanip>
    #include <iostream>
    #include <numbers>
    
    int main() {
     
     std::cout << std::endl;
     
     std::cout<< std::setprecision(10);
     
     std::cout << "std::numbers::e: " << std::numbers::e << std::endl; 
     std::cout << "std::numbers::log2e: " << std::numbers::log2e << std::endl; 
     std::cout << "std::numbers::log10e: " << std::numbers::log10e << std::endl; 
     std::cout << "std::numbers::pi: " << std::numbers::pi << std::endl; 
     std::cout << "std::numbers::inv_pi: " << std::numbers::inv_pi << std::endl;
     std::cout << "std::numbers::inv_sqrtpi: " << std::numbers::inv_sqrtpi << std::endl; 
     std::cout << "std::numbers::ln2: " << std::numbers::ln2 << std::endl; 
     std::cout << "std::numbers::sqrt2: " << std::numbers::sqrt2 << std::endl; 
     std::cout << "std::numbers::sqrt3: " << std::numbers::sqrt3 << std::endl; 
     std::cout << "std::numbers::inv_sqrt3: " << std::numbers::inv_sqrt3 << std::endl;
     std::cout << "std::numbers::egamma: " << std::numbers::egamma << std::endl;
     std::cout << "std::numbers::phi: " << std::numbers::phi << std::endl;
     
     std::cout << std::endl;
     
    }
    

    これは、MSVC コンパイラ 19.27 を使用したプログラムの出力です。

    数学定数は float で利用できます 、 double 、および long double .デフォルト double ごと が使用されますが、 float を指定することもできます (std::numbers::pi_v<float> ) または long double (std::numbers::pi_v<long double> ).

    次は?

    C++20 では、より便利なユーティリティが提供されています。たとえば、どの C++ 機能をサポートしているかをコンパイラに問い合わせて、 std::bind_front, で関数オブジェクトを簡単に作成できます。 または、関数がコンパイル時または実行時に実行されるかどうかにかかわらず、関数内で異なるアクションを実行します。