壊れて、話した数-C++調査者


どのように何回も自分のコード内のハードコード番号を書いている間、これらの番号の単位を表示することができますように確認しようとしているか?おそらく、変数名の一部としてユニットに言及しているか、コメントで、あなたのコードに誰がそれを理解するかを祈っているかもしれません.この記事を読んだ後で、あなたは最も保守的な方法を持ちます.
数字攻撃
私が本当に悪い感じを得たとき、それは午前2時でした.私は、私が私が以前に作成したそのパッチにちょうど何かが起こったということを知っていました.最後に以下の関数を追加しました.
void delay(float milliseconds) { ... }
午前中、オフィスに入って最近の変更をチェックしました.これは私が見たものです.
/*void delay_m(float minutes);
void delay_s(float seconds);
void delay_millis(float milliseconds);
void delay_micros(float microseconds);
void delay_ns(float nanoseconds);*/

// ... A class function ...
    float initial_delay_ns = 1000;
    delay_ns(initial_delay_ns);
    // ...
    float increased_delay_s = 1;
    delay_s(increased_delay_s);
    // ...
    auto current_delay_ns = initial_delay_ns + increased_delay_s * 1e9;
    auto wait_time_m = 2.65;
    delay_m(wait_time_m);
    // ...
    std::cout << "Total delay: " << current_delay_ns + wait_time_m * 60 * 1e9 << std::endl;
// ...
さて、それはこの方法を維持することはできません.そのコードは活動的な犯罪現場に見えます.
数を正義にもたらす
それらの数字は、彼らの弁護士なしで単一の語を話しません-変数.我々は、彼らの層がビジネスのために外出しているときでも、彼らを話す方法を見つける必要があります.コンサルタントを雇うことにした.cppreference . 数日後、彼女は私に次のメッセージを送った.

Hi there.
I heard some words about a way to mark your numbers, so you'll be able to treat them in any way you want to. This mark can leave them as they are and can even transform them into a different type.

Note: This ability also relevant for chars and chars array.

It's called User-defined literals.

Sincerely yours,
cppreference.


ユーザ定義リテラル
この機能は、接尾辞番号、文字と文字列をマークすることが可能になりますので、彼らはより有益と理解できるでしょう.このマークは異なるオブジェクトを生成します.
構文やルールを深くする前に、上記の例がユーザ定義のリテラルでどのように見えるかを見てみましょう.
// void delay(std::chrono::nanoseconds) {}
// ...
    using namespace std::literals::chrono_literals;
    auto initial_delay = 1000ns;
    delay(initial_delay);
    // ...
    auto increased_delay = 1s;
    delay(increased_delay);
    // ...
    auto current_delay = initial_delay + increased_delay;
    auto wait_time = 3min;
    delay(std::chrono::duration_cast<std::chrono::nanoseconds>(wait_time));
    // ...
    std::cout << "Total delay: " << std::chrono::duration_cast<std::chrono::seconds>(current_delay + wait_time).count() << " seconds." << std::endl;
// ...
構文
新しいユーザ定義リテラルを作成するためには、標準的なものが何であれ、次の構文を使用しなければなりません.
[ Constexpr ] [インライン]out_type 演算子"suffix ( in_type );
  • アウトストラップタイプ-あなたが必要な任意のタイプすることができます.
  • サフィックス-あなたが望む文字(ここで適用するいくつかの規則があります)、単純さのために、常に小文字から始まりますcppreference - user literal ).
  • InCount型-以下のパラメータリストのみを許可します.
  • const char*
  • unsigned long long int
  • long double
  • char
  • wchar_t
  • char8_t <高橋潤子>
  • char16_t
  • char32_t
  • const char* , std::size_t
  • const wchar_t* , std::size_t
  • const char8_t* , std::size_t
  • const char16_t* , std::size_t
  • const char32_t* , std::size_t
  • 関数がテンプレート関数である場合、空のパラメータリストを持たなければなりません.
  • -テンプレートは、要素型CHARを持つ非型テンプレートパラメータパックです.
    template <char...> out_type operator"" _suffix();
    
    - C + 20以降、クラス型の非型テンプレートパラメータ(この場合、文字列リテラル演算子テンプレートとして知られています).
    struct A { constexpr A(const char *); };
    template<A a> A operator"" _a();
    
    標準ライブラリ
    使い方
    標準的なユーザー定義のリテラルを使用するにはusing namespace std::; . それがなければ、構文は以下のようになります:
    auto str = std::string_literals::operator""s("aaa", 3);
    // Instead Of: auto str = "aaa"s;
    
    通常、我々は使用しない場合でも、この種の構文を見ることはありませんusing namespace ... これは通常、悪い練習と考えられているので、なぜここでそれを必要とするのですか?
    この質問の答えはADL(引数依存ルックアップ)です.この関数に送信されたパラメータの1つが同じ名前空間から来た場合、コンパイラは関数の名前空間を識別できます.std::cout ).
    文字列リテラルusing namespace std::string_literals;この名前空間から取得するユーザ定義のリテラルはstd::string_literals::operator""s 以下のパラメータを受け取ります.
  • コンストチャー*残高str
  • コンストWcharhorn T *は、残酷なstr、sizehis t
  • コンストシャルル8世*残酷str
  • コンストシャルスラT *シュンサーSTR、Sizen
  • コンストChary 32 Ch T *は、残酷なstr
  • それらの各々は、Aを返しますstd::basic_string 指定した型のオブジェクト.例えば、
    auto str = "Hello String Literals"s;
    
    STR型std::basic_string と同じstd::string . これはユーザ定義リテラルの非常に一般的な使用法です.
    クロノリテラルusing namespace std::chrono_literals;クロノリテラルstd::chrono::duration オブジェクト.以下のユーザ定義リテラルを提供します:h [時間]min [分]s [秒]ms [ミリ秒]us [マイクロ秒]ns [ナノ秒]y [範囲内の特定の年: - 32767 , 32767 ].d [カレンダー内の月の日を表す( 256より小さい値のみ)
    最初のユーザー定義リテラルで見つけることができる使用例この記事では例.
    複雑な文学using namespace std::literals::complex_literals複素リテラルは、仮想部分として与えられた数をゼロに初期化された実数部で返します.利用可能なユーザ定義リテラル
  • i - リターンstd::complex(0, arg);
  • if - リターンstd::complex(0, arg);
  • il - リターンstd::complex(0, arg);
  • int main() {
        using namespace std::complex_literals;
        std::complex<double> c = 1.0 + 1i; // std::complex<double>(1.0, 1.0)
        std::complex<float> z = 3.0f + 4.0if; // std::complex<float>(3.0, 4.0)
    }
    
    ユーザ定義リテラルの例
    角度の例
    class angle {
    public:
        struct degrees {};
        struct radians {};
        constexpr angle(float deg, degrees) { _deg = deg; }
        constexpr angle(float rad, radians) { _deg = rad * 180 / M_PI; }
        [[nodiscard]] constexpr float get_degrees() const { return _deg; }
        [[nodiscard]] constexpr float get_radians() const { return _deg * M_PI / 180; }
    
    private:
        float _deg;
    };
    
    namespace literals {
        constexpr angle operator"" _deg(long double deg) {
            return angle(deg, angle::degrees{});
        }
    
        constexpr angle operator"" _deg(unsigned long long int deg) {
            return angle(deg, angle::degrees{});
        }
    
        constexpr angle operator"" _rad(long double rad) {
            return angle(rad, angle::radians{});
        }
    
        constexpr angle operator"" _rad(unsigned long long int rad) {
            return angle(rad, angle::radians{});
        }
    }
    
    using namespace literals;
    
    int main() {
        constexpr auto deg = 3.14159265_rad;
        constexpr auto rad = 360_deg;
        static_assert(deg.get_degrees() == 180);
        static_assert(rad.get_radians() == (float)(M_PI * 2));
        std::cout << deg.get_degrees() << std::endl; // 180
        std::cout << deg.get_radians() << std::endl; // 3.14..
        std::cout << rad.get_degrees() << std::endl; // 360
        std::cout << rad.get_radians() << std::endl; // 6.28..
        return EXIT_SUCCESS;
    }
    
    日付の例
    日付からの例に基づきますBecome a Compile-Time Coder .
    struct date_offset {
        int d;
        int m;
        int y;
    
        constexpr date_offset(int days, int months, int years) : d(days), m(months), y(years) {}
    
        [[nodiscard]] constexpr date_offset operator+(date_offset ref) const {
            return date_offset(d + ref.d, m + ref.m, y + ref.y);
        }
    
        [[nodiscard]] constexpr date_offset operator-(date_offset ref) const {
            return *this + date_offset(-ref.d, -ref.m, -ref.y);
        }
    };
    
    class date {
    public:
        constexpr date(int day, int month, int year)
                : d(day), m(month), y(year) {
            self_balance();
        }
    
        [[nodiscard]] constexpr date offset(date_offset offset_data) const {
            const auto after_years_offset = date(d, m, y + offset_data.y);
            const auto after_month_offset = date(d, m + offset_data.m, after_years_offset.get_year());
            return date(after_month_offset.get_day() + offset_data.d, after_month_offset.get_month(), after_month_offset.get_year());
        }
    
        [[nodiscard]] constexpr date operator+(date_offset offset_data) const {
            return offset(offset_data);
        }
    
        [[nodiscard]] constexpr unsigned short get_day() const { return d; }
        [[nodiscard]] constexpr unsigned short get_month() const { return m; }
        [[nodiscard]] constexpr unsigned short get_year() const { return y; }
    
    private:
        int d, m, y;
    
        constexpr void self_balance() {
            unsigned short days_in_month;
            unsigned short days_in_prev_month = 31;
            bool is_change_detected = false;
            if (m == 2) {
                if (!(y % 4)) {
                    days_in_month = 29;
                } else {
                    days_in_month = 28;
                }
            } else {
                if (m <= 7 &amp;&amp; m % 2 || m >= 8 &amp;&amp; m % 2 == 0) {
                    days_in_month = 31;
                    if (m != 8) {
                        days_in_prev_month = 30;
                    }
                } else {
                    days_in_month = 30;
                }
            }
    
            if (d > days_in_month) {
                d -= days_in_month;
                m++;
                is_change_detected = true;
            } else if (d < 1) {
                d += days_in_prev_month;
                m--;
                is_change_detected = true;
            }
    
            if (m > 12) {
                m = 1;
                y++;
                is_change_detected = true;
            } else if (m < 1) {
                m = 12 - m;
                y--;
                is_change_detected = true;
            }
    
            if (is_change_detected) self_balance();
        }
    };
    
    namespace literals {
        inline namespace dates_literals {
            constexpr date_offset operator"" _d(unsigned long long int days) {
                return date_offset(days, 0, 0);
            }
    
            constexpr date_offset operator"" _m(unsigned long long int months) {
                return date_offset(0, months, 0);
            }
    
            constexpr date_offset operator"" _y(unsigned long long int years) {
                return date_offset(0, 0, years);
            }
        }
    }
    
    using namespace literals;
    
    int main() {
        constexpr date my_date(23, 8, 2020);
        constexpr auto new_date = my_date + 8_d + 3_m + 5_y;
        std::cout << new_date.get_day() << " / " << new_date.get_month() << " / " << new_date.get_year() << std::endl;
        static_assert(new_date.get_day() == 1 && new_date.get_month() == 12 && new_date.get_year() == 2025);
        return EXIT_SUCCESS;
    }
    
    要約する
    リテラルのない数は、何の意味もなしの数字であり、コードは本当に長期的に維持するのは難しいです.我々は、ユニット間の変換を制御するために、ユーザー定義のリテラルを使用することができますこのように我々は大幅にコードの複雑さを減らすことができ、単位の変換ミスの可能性を減らすことができます.
    この記事はもともと個人的なブログに掲載されています.C++ Senioreas
    完全なサンプルリポジトリcppsenioreas-user-defined-literals