19
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++ の pair の first,second を [] オペレータから呼べるようにした話

Last updated at Posted at 2025-01-22

はじめに

C++ の std::pair<t1,t2> pr; の要素のアクセスの際に pr.firstpr.second と記述するのが面倒だといった感じの話を、X (旧 Twitter) で見かけました。

それなら、operator[]pr[0]pr[1] のようにして要素を読み書きできるようなペアを自作すれば良いではないですか!!!

話のミソ

ここで問題になるのが、pr.firstpr.second はそれぞれ t1 型と t2 型なので、同じ operator[] によって定義することができないということです。

期待する挙動としては、pr[0]pr.first の参照を返し、pr[1]pr.second の参照を返すようになることです。つまり、実引数の値によって呼び出す operator[] を分岐させるということです!!!

結論から言うと、今の C++ では 0nullptr にキャスト可能なことに注目して、0 用と 1 用のそれぞれに operator[] を別々に定義することを試みます。

std::pair<t1,t2> pr; をラップする自作クラスを作り、その中に 0 用の operator[] を以下のようにして定義してみます。

t1& operator[](nullptr_t i){
    return m_pr.first;
}

0 に関してはこれで良いのですが、1 用の operator[] の定義で少し困ります。1int なので例えば以下のように 1 用の operator[] を定義することを試みます。

t2& operator[](int i){
    return m_pr.second;
}

残念、これでは全然ダメです。なぜなら 0 も当然 int なので、0 $\rightarrow$ nullptr 用の operator[] ではなく上記の 1 用の operator[] に吸われてしまうからです。

そこで唐突ですが、0 が吸われてしまわないように、実引数 0 に対して優先度が低い operator[] を作ることにします。実は、C++ のキャストには優先順位があるらしいです (前の Twitter アカウントで @kakurenboUni さんに教えてもらいました)。

少し試したところ、int $\rightarrow$ 自作クラス のキャストは 0nullptr にキャストされるよりも優先順位が低そうな雰囲気を感じたので、それを実装します。

struct _dummy_{
    _dummy_(int x){
        assert(x == 1);
    }
};

t2& operator[](_dummy_ i) {
    return m_pr.second;
}

このようにすることで、operator[](i) の実引数 i0 の場合は優先度が高い operator[](nullptr_t) が呼ばれ、0 以外の int の場合は nullptr にキャスト不可能なので operator[](_dummy_) が呼ばれます。最後に、i01 しかとらないので、_dummy_ のコンストラクタが 1 以外を受け取るとエラーになるようにしましょう。

このようにして、実引数の値によってオペレータの呼び出しを分岐させることができました!!!!

コード例

雑に std:pair<t1,t2> をラップしたクラスを貼っておきます。自由に使ってください。

#include <iostream>
#include <cassert>
using namespace std;



template<typename t1 , typename t2>
struct mpair{
    protected:
    pair<t1,t2> m_pr;
    public:
    mpair(){}
    explicit mpair(pair<t1,t2> pr_) : m_pr(pr_){}
    mpair(t1 first , t2 second) : m_pr(first,second) {}
    explicit operator pair<t1,t2>(){return m_pr;}

    /*
        [0] -> t1
        0 は nullptr_t にキャスト可能なので、もう一つの operator[] よりも優先度を上げるとこっちが呼ばれる
    */
    t1& operator[](nullptr_t i){
        return m_pr.first;
    }

    // 自作クラスを使って優先度を下げる。
    struct _dummy_{
        _dummy_(int x){
            assert(x == 1);
        }
    };
    // [1] -> t2 
    t2& operator[](_dummy_ i) {
        return m_pr.second;
    }

    friend bool operator==(const mpair<t1,t2>& a , const mpair<t1,t2>& b){return a.m_pr == b.m_pr;}
    friend bool operator!=(const mpair<t1,t2>& a , const mpair<t1,t2>& b){return !(a==b);}
    friend partial_ordering operator<=>(const mpair<t1,t2>& a , const mpair<t1,t2>& b){return a.m_pr <=> b.m_pr;}
};





int main(){
    mpair<string,int> pr;
    pr[0] = "okoteiyu";
    pr[1] = 25;
    cout << pr[0] << " is " << pr[1] << " years old now." << endl;
}
19
8
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?