はじめに
C++ の std::pair<t1,t2> pr;
の要素のアクセスの際に pr.first
と pr.second
と記述するのが面倒だといった感じの話を、X (旧 Twitter) で見かけました。
それなら、operator[]
で pr[0]
や pr[1]
のようにして要素を読み書きできるようなペアを自作すれば良いではないですか!!!
話のミソ
ここで問題になるのが、pr.first
と pr.second
はそれぞれ t1
型と t2
型なので、同じ operator[]
によって定義することができないということです。
期待する挙動としては、pr[0]
が pr.first
の参照を返し、pr[1]
が pr.second
の参照を返すようになることです。つまり、実引数の値によって呼び出す operator[]
を分岐させるということです!!!
結論から言うと、今の C++ では 0
が nullptr
にキャスト可能なことに注目して、0
用と 1
用のそれぞれに operator[]
を別々に定義することを試みます。
std::pair<t1,t2> pr;
をラップする自作クラスを作り、その中に 0
用の operator[]
を以下のようにして定義してみます。
t1& operator[](nullptr_t i){
return m_pr.first;
}
0
に関してはこれで良いのですが、1
用の operator[]
の定義で少し困ります。1
は int
なので例えば以下のように 1
用の operator[]
を定義することを試みます。
t2& operator[](int i){
return m_pr.second;
}
残念、これでは全然ダメです。なぜなら 0
も当然 int
なので、0
$\rightarrow$ nullptr
用の operator[]
ではなく上記の 1
用の operator[]
に吸われてしまうからです。
そこで唐突ですが、0
が吸われてしまわないように、実引数 0
に対して優先度が低い operator[]
を作ることにします。実は、C++ のキャストには優先順位があるらしいです (前の Twitter アカウントで @kakurenboUni さんに教えてもらいました)。
この辺りでしょうか (かなり複雑ですが、優先順位があります)https://t.co/dWcY5nWc7N
— 🦠みどりむし@未証明 (@KakurenboUni) May 5, 2024
少し試したところ、int $\rightarrow$ 自作クラス のキャストは 0
が nullptr
にキャストされるよりも優先順位が低そうな雰囲気を感じたので、それを実装します。
struct _dummy_{
_dummy_(int x){
assert(x == 1);
}
};
t2& operator[](_dummy_ i) {
return m_pr.second;
}
このようにすることで、operator[](i)
の実引数 i
が 0
の場合は優先度が高い operator[](nullptr_t)
が呼ばれ、0
以外の int
の場合は nullptr
にキャスト不可能なので operator[](_dummy_)
が呼ばれます。最後に、i
は 0
か 1
しかとらないので、_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;
}