C++
C++11
初心者
初心者向け
演算子

演算子のオーバーロードで幸せになろう!!

はじめに

私はC++を学び始めたばかりで、間違えていることを書くこともあります。このQiitaは私の備忘録のようなもので、私と同じ初心者がこれらをみることで少しでも役に立てたらという想いで公開するかもしれません。

編集リクエストでの誤りの訂正非常にありがたいです......

演算子のオーバーロードはいいぞ

C++にはオーバーロードできる演算子がたくさんあります。それらをオーバーロードすることで例えばクラスや構造体といったオブジェクトをより便利に扱うことが出来る可能性があります。
この記事では分数を表すfractionクラスを作成し、演算子のオーバーロードの幸せスパイラルを体感したいと思います。

演算子をオーバーロードする方法

演算子をオーバーロードする方法はとても簡単です。ここではメンバ関数としてオーバーロードする方法を示します。構文はこうです。type operator operator-symbol ( parameter-list )。しかし、キャスト演算子だけはoperator type()となり、ほかとは異なります。
例えば、class hogeの代入演算子とキャスト演算子をオーバーロードする時はこのように書きます。

#include <cstdint>
class hoge{
    hoge & operator=(const hoge & src) { /* 実装 */ }
    operator std::int64_t() const { return 0; }
};

演算子たちの紹介

C++でオーバーロードできる演算子たちの紹介です。とても多いのでほかの方のQiitaを見てください。
C++演算子オーバーロード大全

とても多いです。この記事では全てを紹介することはありません。私も含めた初心者が演算子をオーバーロードすると幸せになれるということを分かってもらうことが目的です。

fractionクラス

先ほどfractionを作って演算子オーバーロードの幸せスパイラルを云々といったのでおとなしくfractionクラスを書きます。

fraction.h
#include <cstdint>
#include <cstdlib>
// 最大公約数を求める(分数を約分するときに使う)
inline std::int64_t gcd(long long a, long long b) {
    long long r = a % b;
    if(r) return gcd(b, r);
    return b;
}

// 分数クラス
class fraction {
    std::int64_t mDenominator;  // 分母
    std::int64_t mNumerator;    // 分子
public:
    constexpr fraction(std::int64_t denominator, std::int64_t molecule) : mDenominator(denominator), mNumerator(molecule) {}

    constexpr bool isMinus() const noexcept {
        return (mDenominator ^ mNumerator) >> 63 & 1;
    }
    void reduce() {
        auto gcf = gcd(std::abs(mNumerator), std::abs(mDenominator));
        mDenominator /= gcf;
        mNumerator /= gcf;
        if (isMinus()) {
            mDenominator = abs(mDenominator);
            mNumerator = -abs(mNumerator);
        }
    }
    // 分母、分子の順
    void set(std::int64_t deno, std::int64_t mole) noexcept {
        mDenominator = deno;
        mNumerator = mole;
    }
    constexpr double asdouble() const {
        return mNumerator / (double)mDenominator;
    }
    void get(std::int64_t & deno, std::int64_t & mole) const noexcept {
        deno = mDenominator; mole = mNumerator;
    }
    constexpr fraction() : mDenominator(1), mNumerator(0) {}
    //~fraction() = default;
};

こういうクラスも別に悪いことはないですが、演算子をオーバーロードするともっと幸せになれます。
例えば、asdouble()メソッドはキャスト演算子をオーバーロードすることによってより便利な形に生まれ変わります。

class fraction{
// 前略
    // double asdouble() const { return mNumerator / (double)mDenominator; }
    operator double() const { return mNumerator / (double)mDenominator; }
// 後略
};

このように書くことが出来ればdoubleを引数として受け取る関数などにfractionクラスのデータを渡す時、hoge(Fraction.asdouble());という関数呼び出しはhoge(Fraction);と書くことが出来るようになります。とても便利だと思わないならばきっと貴方は人間ではないでしょう。

この調子で比較演算子や等値演算子などもオーバーロードしていけば、vectorコンテナに入ったfractionクラスもalgorithmヘッダに入っている関数で操作できます。ソートや探索のために専用のラムダ式を書く必要はありません。

最終的に完成したものがこちらになります
fraciton.h
#pragma once
#include <exception>
#include <stdexcept>
#include <stdlib.h>
#include <math.h>
namespace myUtil {

    constexpr int_fast64_t mantissa(double ld) {
        auto m = (1LL << 52) + (0xfffffffffffff & *(int_fast64_t*)&ld);
        return (ld < 0) ? -m : m;
    }
    constexpr int_fast64_t index(double ld) {
        //1 << (52 - i)
        return ((*(int_fast64_t*)&ld >> 52) & 0x7ffLL) - 1023;
    }

    constexpr int_fast64_t gcd(int_fast64_t a, int_fast64_t b) {
        return (a % b) ? gcd(b, a % b) : b;
    }

    constexpr int_fast64_t lcm(int_fast64_t a, int_fast64_t b) {
        return a * b / gcd(a, b);
    }

    class fraction {
        int_fast64_t mDenominator;
        int_fast64_t mNumerator;
    public:
        constexpr void validate() const {
            if (mDenominator == 0) throw std::overflow_error("invalid denominator");
        }
        explicit constexpr operator double() const {
            validate();
            return (double)mNumerator / (double)mDenominator;
        }
        explicit constexpr operator int_fast64_t() const {
            return (int_fast64_t)(double)*this;
        }
        bool operator==(const fraction & src) const noexcept {
            fraction a{ fraction::reduce(*this) }, b{ fraction::reduce(src) };
            return a.mDenominator == b.mDenominator && a.mNumerator == b.mNumerator;
        }
        bool operator!=(const fraction & src) const noexcept { return !(*this == src); }
        fraction operator-() const noexcept { return fraction(mDenominator, -mNumerator); }
        fraction operator+(const fraction & b) const noexcept {
            auto newDen = lcm(abs(mDenominator), abs(b.mDenominator));
            auto newNum = (isMinus() ? -_abs64(mNumerator) : mNumerator) * newDen / mDenominator +
                (b.isMinus() ? -_abs64(b.mNumerator) : b.mNumerator) * newDen / b.mDenominator;
            fraction f{ newDen, newNum };
            f.reduce();
            return f;
        }
        fraction operator-(const fraction & b) const noexcept {
            return *this + -b;
        }
        fraction operator*(const fraction & b) const {
            fraction f;
            f.mDenominator = this->mDenominator * b.mDenominator;
            f.mNumerator = this->mNumerator * b.mNumerator;
            f.reduce();
            return f;
        }
        fraction operator/(const fraction & b) const {
            fraction f;
            f = *this * b.reciprocal();
            f.reduce();
            return f;
        }
        fraction & operator=(int_fast64_t b) noexcept {
            mDenominator = 1;
            mNumerator = b;
            return *this;
        }
        fraction & operator=(long double b) {
            myUtil::fraction f;
            set(myUtil::index(b), myUtil::mantissa(b));
            reduce();
            return *this;
        }

        constexpr bool isMinus() const noexcept {
            return (mDenominator ^ mNumerator) >> 63 & 1;
        }
        constexpr void reduce() {
            auto gcf = gcd(abs(mNumerator), abs(mDenominator));
            mDenominator /= gcf;
            mNumerator /= gcf;
            if (isMinus()) {
                mDenominator = abs(mDenominator);
                mNumerator = -abs(mNumerator);
            }
        }
        static fraction reduce(const fraction & f) {
            fraction cp{ f };
            cp.reduce();
            return cp;
        }
        fraction reciprocal() const {
            if (mNumerator == 0)  throw std::overflow_error("invalid denominator");
            return fraction{ this->mNumerator, this->mDenominator };
        }
        bool set(int_fast64_t deno, int_fast64_t nume) noexcept {
            if (!deno) return false;
            mDenominator = deno;
            mNumerator = nume;
            return true;
        }
        bool set(std::pair<int_fast64_t, int_fast64_t> pack) {
            return set(pack.first, pack.second);
        }
        void get(int_fast64_t & deno, int_fast64_t & nume) const noexcept {
            deno = mDenominator; nume = mNumerator;
        }
        std::pair<int_fast64_t, int_fast64_t> get() const noexcept {
            return std::make_pair(mDenominator, mNumerator);
        }
        constexpr fraction(int_fast64_t denominator, int_fast64_t numerator) :mDenominator(denominator), mNumerator(numerator) { validate(); }
        constexpr fraction() : mDenominator(1), mNumerator(0) {}
        explicit constexpr fraction(long double d) : mDenominator{ index(d) < 0 ? 1LL << (52 - index(d)) : 1LL << 52 }, mNumerator{ index(d) < 0 ? mantissa(d) : mantissa(d) << index(d) } { this->reduce(); }
        explicit constexpr fraction(int_fast64_t l) : mDenominator{ 1 }, mNumerator{ l } {}
        ~fraction() = default;
    };
}

inline namespace myUtilLiterals {
    inline myUtil::fraction operator"" _fr(unsigned long long a) {
        return myUtil::fraction(1, a);
    }
    inline myUtil::fraction operator"" _fr(long double a) {
        myUtil::fraction f;
        auto i = myUtil::index(a);
        auto m = myUtil::mantissa(a);

        f.set(i < 0 ? 1LL << (52 - i) : 1LL << 52, i < 0 ? m : m << i);
        f.reduce();
        return f;
    }
}

ちなみに

とても便利なのでここで紹介しますが、関数呼び出し演算子(())をオーバーロードするとstd::functionで保持することが出来る関数オブジェクトになります。
標準ライブラリではrandomヘッダのmersenne_twister_engineをはじめとする全ての乱数生成器で関数呼び出し演算子がオーバーロードされています。
そのため関数オブジェクトの配列をつくり、状況によって処理を変えるということも非常に簡単にできます。

#include <array>
#include <iostream>
#include <functional>
#include <random>
#include <cstdint>


int main() {
    std::size_t t;
    std::array<std::function<std::uint_fast32_t()>, 3> randNumGen{ std::minstd_rand{}, std::mt19937{}, std::ranlux24_base{} };
    std::cout << "choose random number generator" << std::endl << "0:linear_congruential" << std::endl << "1:mt19937" << std::endl << "2:subtract_with_carry" << std::endl;
    std::cin >> t;
    t %= 3;
    for (int i = 0; i < 10; i++) std::cout << randNumGen.at(t)() << std::endl;
    return 0;
}