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

tgmath.h の話

Program C

このプレゼンで紹介した tgmath.h の実装が素晴らしいという話なんですが、資料は口頭で説明する前提すぎて、イマイチ詳しくないので別に書いておこうかなぁと思います。これは C で型計算やってると言えると思うのでなかなかなもんなのです。

そもそも tgmath.h というヘッダが何かと言うと、 Type-generic math の略で、 C99 の PDF の 7.22 に書いてあります。ちなみにたぶん私はこの PDF を参照してます。

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

書いてあることは以下 4 点:

  1. tgmath.h を #include すると math.h と complex.h も #include されて type-generic なマクロを定義しまっせ
  2. sinf やら sinl みたいなのじゃなくて sin とかが呼ばれた時は float が引数なら sinf などと適切に dispatch されますよ
  3. 一つ以上の引数が long doubleなら long double バージョンが呼ばれます。一つ以上の引数が double か整数型なら double バージョンが呼ばれます。それ以外の場合 float バージョンが呼ばれます
  4. _Complex に対しては csin とかみたいに c prefix がついてるバージョンが呼ばれますよ。 cabs は例外的に fabs が generic version ですよ

実際に使ってみるとこんな感じ:

#include <stdio.h>
#include <tgmath.h>

int main() {
    float f;
    double d;
    _Complex c;
    printf("%f\n", sin(f));
    printf("%f\n", sin(d));
    printf("%f\n", sin(c));
}

これでできた tgmath.o に対して nm とかしてやると、

% nm tgmath.o
                 U csin
0000000000000000 T main
                 U printf
                 U sin
                 U sinf

などと、 sin, sinf, csin がそれぞれ参照されてることがわかります。 tgmath.h を #inlude せずに math.h と complex.h だけを #include した場合は sin だけしか参照されません。

で、使う方としてはまぁこれで十分なのですが、実装を考えると、 C にはオーバーロードとか無いわけで、あまり自明ではありません。

その時のいきさつは Joseph S. Myers さんの日記にあるようです。

http://www.advogato.org/person/jsm28/diary/1.html

これによると、最初は GCC 拡張の __typeof__ と statement expression でやっていたのだけど、整数型の扱いがおかしかったとのこと。でまぁどうすっかなーと gcc ML と相談して考えた結果、 GCC に __builtin_classify_type という拡張を入れてやろうということになったそうです。実際この拡張が入ってる GCC ではこれを使うようになっているようです。

しかし 個性的な人として有名な(オブラート) Ulrich Drepper さんは __typeof__ だけで頑張るコード書きましたよーというのがこの紹介したコードで。

 /* This is ugly but unless gcc gets appropriate builtins we have to do
   something like this.  Don't ask how it works.  */


 /* 1 if 'type' is a floating type, 0 if 'type' is an integer type.
   Allows for _Bool.  Expands to an integer constant expression.  */
#define __floating_type(type) (((type) 0.25) && ((type) 0.25 - 1))


 /* The tgmath real type for T, where E is 0 if T is an integer type and
   1 for a floating type.  */
#define __tgmath_real_type_sub(T, E) \
  __typeof__(*(0 ? (__typeof__(0 ? (double *)0 : (void *)(E)))0               \
                 : (__typeof__(0 ? (T *)0 : (void *)(!(E))))0))


 /* The tgmath real type of EXPR.  */
#define __tgmath_real_type(expr) \
  __tgmath_real_type_sub(__typeof__(expr), __floating_type(__typeof__(expr)))

というもの。このコードの __tgmath_real_type というマクロはどうやら、浮動小数型 T を渡したらその浮動小数型 T を、整数型を渡したら double をかえすような型に関しての関数になってるようです。

 /* 1 if 'type' is a floating type, 0 if 'type' is an integer type.
   Allows for _Bool.  Expands to an integer constant expression.  */
#define __floating_type(type) (((type) 0.25) && ((type) 0.25 - 1))

の __floating_type という型が浮動小数点型なら 1 を返して整数型なら 0 をかえすマクロはスライドに書いた通り、

(int)0.25 == 0
(_Bool)0.25 == 1
(float)0.25 == 0.25f
(double)0.25 == 0.25

を考えれば簡単に理解できるかと思います。

問題は次の __tgmath_real_type_sub というマクロで、さっきの C99 の PDF の 6.5.15.6 にある条件演算子の型についてのルールのうち、

  1. 片方が null pointer constant (要は (void*)0) であればもう一方の型を採用する
  2. 片方が void* でもう一方が pointer なら void* を採用する

の2つのルールをうまく活用しています。具体的に最初のの条件演算子を取り出して考えてやると、

0 ? (double *)0 : (void *)(E)

となっていて E は __floating_type の結果なので浮動小数なら 1 で整数型なら 0 。浮動小数の場合、右辺は (void *)(1) となって両辺ともに null pointer constant で無いので ((double*)0 は null pointer constant では無いことに注意) 、二つ目のルールが適用されて、この条件演算子全体の型は void* になります。整数の場合を考えると、右辺は (void*)(0) 、これは null pointer constant なので、 1 つ目のルールが適用されて、この条件演算子全体の型はもう一方の型である double* が採用されます。

二つ目の条件演算子はそれが ! によって逆になった格好です。

0 ? (T *)0 : (void *)(!(E))

ここで T は引数として与えた型なので、さっきと逆の動作になって、浮動小数が渡されるとその型が、整数型が渡されると void* がその型になるような条件演算子になっています。

この 2 つの条件演算子がまたまとめられて、

0 ? (一つ目の条件演算子の型)0 : (二つ目の条件演算子の型)0

となっているのが __tgmath_real_type_sub の全体像です。この条件演算子は両方 0 に対してキャストしているので、片方の型が void* なら null pointer constant ということになり、一つ目のルールによって、もう一方の型が全体の条件式の型となります。

で、浮動小数の場合は一つ目の条件演算子は void* 、二つ目は T* となっているので、 T* となり、整数の場合は double* と T* になっているので、無事当初の目的を果たす型関数ができました、ということになります。

ていうか tgmath.h って C99 作ってる人らはどう実装すればいいと思ってたんだろうか…ということで rationale をチラ見。

http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf

 The implementation might, as an extension, endow appropriate ones of
 the macros that this standard specifies only for real arguments with
 the ability to invoke the complex functions.

 This specification does not prescribe any particular implementation
 mechanism for generic macros. It could be implemented simply with
 built-in macros. The generic macro for sqrt, for example, could be
 implemented with

         #undef sqrt
         #define sqrt(x) _ _BUILTIN_GENERIC_sqrt(x)

 Generic macros are designed for a useful level of consistency with
 C++ overloaded math functions.

ということなので、それぞれの関数ごとに compiler の拡張作ってやればいいんじゃねーと考えてたっぽいですね。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h