【闇】【黒魔術】【C++】絶対にやってはいけない inf テク
皆さんこんばんは.今回はちょっと好まれなさそうなC++の使い方を紹介します.
たとえばある int 型配列の最小値が知りたいとき,皆さんはどうしていますか.
std::vector<int> a = something; int min = 100000000; /* なんか大きな値 */ for(auto i : a){ min = std::min(min, i); }
のようにすると思います.この記事のテーマはこの「100000000; /* なんか大きな値 */」のところです.
いつだったかテレビに登場したソースコード内に 1145141919810893364364 という数値が登場して話題になったことがありました(2chのスレ)が,このように「なんか大きな値」はプログラムを書いているとしばしば必要になります.
多くの場合 inf といった名前の constexpr 変数に格納(あるいはdefine)されることが多いですが, 32bit整数型や64bit整数型,浮動小数点型などによって異なる値を使わなければならないため,linf,INF などと変数が増えていってしまいます.
それを † 闇 C + + † の力で解決してしまおう,というのが今回の記事です.つまり
int a = inf; // int型に収まる大きな値が代入される long long b = inf; // long long型に収まる大きな値が代入される double c = inf; // double型に収まる大きな値が代入される
というように,代入式の左辺の型によって代入する値を変えたいわけです.
これはキャスト演算子をオーバーロードすることによって可能になります.
struct Inf{ constexpr operator int(){ return INT_MAX; //INT_MAXは<climits>で定義されている int の最大値 } };
としておけば,
Inf inf; int a = inf;
とすることで inf::operator int() が呼ばれて a にはその返り値である INT_MAX が代入されるわけです.ちなみに inf::operator int() を static にすることはできないみたいですね.
struct Inf の定義と inf の宣言をまとめると,
struct{ constexpr operator int(){ return INT_MAX; } } inf;
となります.またconstexprが付いているので
constexpr int a = inf;
ということも可能です.
こうして色々な型の最大値に化けられる inf を作ることができました.
今度はこれに負号を付けて -inf とすると最小値が得られるようにしてみましょう.つまり
int a = -inf;
としたときに,-INT_MAX = -(2^31 - 1) ではなく INT_MIN = -2^31 が代入されるようにしたいわけです.
今度は inf.operator-() をオーバーロードして
constexpr auto operator-(){ struct{ constexpr operator int(){ return INT_MIN; } } ret; return ret; }
とすれば良いです.inf::operator-() が呼ばれて ret が返る → ret.operator int() が呼ばれて INT_MIN が返る → a に INT_MIN が代入されるという流れになります.
ちなみに各型の最大値は次のようになっています.
型 | 最大値 | 最小値 | ヘッダ |
unsigned char | UCHAR_MAX | climits | |
signed char | SCHAR_MAX | SCHAR_MIN | climits |
char | CHAR_MAX | CHAR_MIN | climits |
unsigned short | USHRT_MAX | climits | |
short | SHRT_MAX | SHRT_MIN | climits |
unsigned int | UINT_MAX | climits | |
int | INT_MAX | INT_MIN | climits |
unsigned long | ULONG_MAX | climits | |
long | LONG_MAX | LONG_MIN | climits |
unsigned long long | ULLONG_MAX | climits | |
long long | LLONG_MAX | LLONG_MIN | climits |
float | FLT_MAX | cfloat | |
double | DBL_MAX | cfloat | |
long double | LDBL_MAX | cfloat |
(訂正 浮動小数点型の最小値のところにFLT_MIN,DBL_MIN,LDBL_MINを書いていましたがこれらは正の最小値でした)
また,cstdintヘッダのint32_tなどはこれらの型のエイリアスとして定義されるため,例えば using int32_t = int; となっていた場合 operator int() と operator int32_t() を別々に定義することはできません.
これらを全てまとめると次のようになります.
struct Inf{ constexpr operator unsigned char(){ return UCHAR_MAX; } constexpr operator signed char(){ return SCHAR_MAX; } constexpr operator char(){ return CHAR_MAX; } constexpr operator unsigned short(){ return USHRT_MAX; } constexpr operator short(){ return SHRT_MAX; } constexpr operator unsigned int(){ return UINT_MAX; } constexpr operator int(){ return INT_MAX; } constexpr operator unsigned long(){ return ULONG_MAX; } constexpr operator long(){ return LONG_MAX; } constexpr operator unsigned long long(){ return ULLONG_MAX; } constexpr operator long long(){ return LLONG_MAX; } constexpr operator float(){ return FLT_MAX; } constexpr operator double(){ return DBL_MAX; } constexpr operator long double(){ return LDBL_MAX; } constexpr auto operator-(){ struct{ constexpr operator char(){ return CHAR_MIN; } constexpr operator signed char(){ return SCHAR_MIN; } constexpr operator short(){ return SHRT_MIN; } constexpr operator int(){ return INT_MIN; } constexpr operator long(){ return LONG_MIN; } constexpr operator long long(){ return LLONG_MIN; } } ret; return ret; } } inf;
追記
何人かの方に std::numeric_limits の使用を勧められたためそれを使って書き換えてみます.
std::numeric_limitsを使ってmax,minを得る方法としては
std::numeric_limits<int>::max() // INT_MAX std::numeric_limits<double>::lowest() // double型の最も低い値
などのようになります.(訂正,minではなくlowestでした.)
ただこれをそのまま使ってしまうと「inf + inf でオーバーロードしてしまったため inf の値を半分にする」ということが即座にできないため実際に使用する場合やはりキャスト演算子をもつ構造体を作っておく必要があります.
maxについては次のようにすれば長かった部分を短くすることができます.
struct{ template<class T> constexpr operator T(){ return std::numeric_limits<T>::max(); } } inf;
ただしこのようにtemplateを使おうとすると,負号を付けたときの処理を上のようにローカル構造体で片付けることができないため,負の inf についても構造体を作る必要があります.
struct{ template<class T> constexpr operator T(){ return std::numeric_limits<T>::lowest(); } } negative_inf;
そしてこれらにメンバ operator-() を持たせて,-inf が negative_inf に,-negative_inf が inf になるようにすれば良いです.
全体では次のようになります.
struct{ template<class T> constexpr operator T(){ return std::numeric_limits<T>::max(); } constexpr auto operator-(); } inf; struct{ template<class T> constexpr operator T(){ return std::numeric_limits<T>::lowest(); } constexpr auto operator-(); } negative_inf; constexpr auto decltype(inf)::operator-(){ return negative_inf; } constexpr auto decltype(negative_inf)::operator-(){ return inf; }