読者です 読者をやめる 読者になる 読者になる

わくわくとオーボエ

プログラミングの話題とかとか

引数が数値がかどうか判定(SFINAE)

C++ プログラミング

前回に引き続きC++の型まわりで。
整数でも実数でも良いから、とにかく数値を引数にとりたいときとかってありません?自分は時々あります。画像処理をしている時とか。
常にdoubleで扱うのもいいですけど、せっかく整数で扱えるなら整数のまま扱いたい。でもかといって、int用とdouble用みたいに分けたくない。そんなもやもやな時が。

やりたいこと

引数が数値かどうかによって処理を分岐する。
実行時ではなくコンパイル時に分岐をする。

実現方法

SFINAEという原理(?)を使い、template関数に対する以下のどちらかで実現する。

  1. 関数の(普通の)引数のオーバーロード解決を利用する
  2. テンプレート引数のオーバーロード解決を利用する。

この記事では、算術型 = 数値型 とみなす。厳密には、四則演算が定義されているかどうかなので違うけど、四則演算できるならまぁ良いじゃん的な。

先にコードを

SFINAEって?

templateクラスやtemplate関数のオーバーロードの解決方法の方針。
「当てはまりそうだったから試したけどだめでした!」→「よし次試すかー」
そんな感じで順に試していくというだけで、たいして難しい考え方ではないと思う。コンパイラ自体の実装は大変そうだけど。
詳しいことは各自検索で。以下のサイトなどを参考に。

d.hatena.ne.jp

数値かどうかの判定箇所

template関数の普通のオーバーロード解決 : 上のコードでいう f()
数値の場合

関数の引数に注目。第2引数の型が、std::enable_if<std::is_arithmetic<T>::value>::type* になっていますね。

void f(T arg, typename std::enable_if<std::is_arithmetic<T>::value>::type* t = 0)

std::is_arithmetic<T>::value は、Tが算術型だったときはtrueに、それ以外の時はfalseになります。つまり、たとえばTがintであれば(コンパイルの途中で)以下のように変換されます。

void f(int arg, typename std::enable_if<true>::type* t = 0)

ところで、std::enable_if<bool>::typeはテンプレート引数がtrueかfalseかによって定義変わります。 std::enable_if<true>は(template第2引数で特別指定をしなければ)void定義として定義るので、これはさらに

void f(int arg, void* t = 0)

に変換されます。なんか見慣れた感じに落ち着きましたね。(C++ではvoid*ってあまり使わないですけど。)問題なく展開できたので、f(3)とか書かれてたらこの関数が呼ばれます。

非数値(正確には非算術型)の場合

はじめに

void f(T arg, typename std::enable_if<std::is_arithmetic<T>::value>::type* t = 0)

を適用しようとすることには変わりません。そして、Tが非算術型の場合、std::is_arithmetic<T>::valueはfalseになるので、 std::enable_if<false>::type で定義される型を求めようとします。
しかし、 std::enable_if<false>にはtypeというものが定義されていません(・ω・;)こまりましたねー
すると、コンパイラは諦めて他のオーバーロード候補が無いか探します。他に無ければコンパイルエラーになります。これで、templateを使いつつも一部の型だけに適応することができますね!

今回は非数値でも処理ができるように、別途次のようにオーバーロードを定義しています。

void f(T arg, typename std::enable_if<!std::is_arithmetic<T>::value>::type* t = 0)

見た目ややこしいですが、std::is_arithmetic<T>::valueの結果を!で反転させています。だから、数値でないものに対してはこちらのオーバーロードが無事解決できるようになります。

template引数のオーバーロード解決 : 上のコードでいう g()

(普通の)引数は、これ以外にもオーバーロードしたいことがよくある。template引数を使って分岐した方が間違いが少なくなるし、型で分岐してることがなんとなく伝わりやすい気がする。

実装での基本的な考え方は同じなので省略。ただ、こちらではダミー変数を使ってあげないと、デフォルト引数を指定できないらしい。extern指定すれば、他ファイルに影響したり無駄なメモリが確保されたりもしないので、問題無いと言えば問題ない。

その他

数値判定以外にも、いろいろな判定をできるメタ関数が標準で揃っているみたい。以下の記事にまとまっている。

qiita.com

あとは、記事というかcppreferenceにはやっぱり必要な情報は載っていますね。

Type support (basic types, RTTI, type traits) - cppreference.com

まとめ

  1. template使うと対象の型をコンパイル時に絞り込めたりする。
  2. 可能性は無限大、可読性は微妙。
こちらも参照ください

qiita.com