星にゃーんのブログ

ほとんど無害。

C言語でmapしたかった

※注意: この記事に実用性はありません。

C言語で遊んでたら、突然写像の方のmapを書きたくなったので、ggってみた。

するとありがたいことにこんな記事が。 ワンダープラネット株式会社(WonderPlanet Inc.)

でも、配列の長さをいちいち指定しないといけないのがめんどくさい。

というわけで、マクロにしてみた。

#include <stdio.h>

int square(int n) {
  return n * n;
}

int* raw_int_map(const int *source, int *result, size_t n, int (*func)(int)) {
  for (int i = 0; i < n; i++) {
    result[i] = func(source[i]);
  }
  return result;
}

// マクロでラップ
#define map(SOURCE, RESULT, FUNC)\
  int RESULT[sizeof(SOURCE) / sizeof(SOURCE[0])] = {0};\
  raw_int_map(SOURCE, RESULT, sizeof(SOURCE) / sizeof(SOURCE[0]), FUNC);

int main(void) {
  int numbers[] = {1, 2, 3, 4, 5};
  map(numbers, squares, (int (*)(int))square);
  for (int i = 0; i < 5; i++) {
    printf("%d ", squares[i]);
  }
  return 0;
}

マクロの可能性を感じた。 要素の型をマクロの引数にしたら多相型関数っぽいこともできそう。

結論: Cのマクロ怖い

C言語で多相のmap

おまけ カッとなってやった

C言語で多相もどきとmap · GitHub

多分この記事はn番煎じです。

2021-01-29追記:

アクセス解析を見るとちょくちょく閲覧されてるようなので、この記事の内容について補足する。

もちろん、このマクロは実用的ではない。 具体的には、以下のように配列を関数の引数に渡すケースでは、たとえ仮引数をint int_nums[5]のように書いていたとしてもバグる。

int print_int(int n) {
  return printf("%d", n);
}

void print_array(int int_nums[5]) {
  map(int, int_nums, result, print_int);
}

これは、関数の引数に配列が渡される際、ポインタ型に型変換されるためだ。その結果、sizeof(SOURCE) / sizeof(SOURCE[0])の結果が期待通り(配列の要素数)にならない。暗黙の型変換はかなり挙動がややこしいので、これ以上具体的な動作については言及しないが、例えばclang -Wallコンパイルすると以下のようなかなりゴツい警告が出る。

main.c:22:3: warning: sizeof on array function parameter will return size of 'int *' instead of 'int [5]' [-Wsizeof-array-argument]
  map(int, int_nums, result, print_int);
  ^
main.c:4:21: note: expanded from macro 'map'
  TYPE RESULT[sizeof(SOURCE) / sizeof(SOURCE[0])] = {0};\
                    ^
main.c:21:22: note: declared here
void print_array(int int_nums[5]) {
                     ^
main.c:22:3: warning: 'sizeof (int_nums)' will return the size of the pointer, not the array itself [-Wsizeof-pointer-div]
  map(int, int_nums, result, print_int);
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.c:4:30: note: expanded from macro 'map'
  TYPE RESULT[sizeof(SOURCE) / sizeof(SOURCE[0])] = {0};\
              ~~~~~~~~~~~~~~ ^
main.c:21:22: note: pointer 'int_nums' declared here
void print_array(int int_nums[5]) {
                     ^
main.c:22:3: warning: sizeof on array function parameter will return size of 'int *' instead of 'int [5]' [-Wsizeof-array-argument]
  map(int, int_nums, result, print_int);
  ^
main.c:5:30: note: expanded from macro 'map'
  for (int i = 0; i < (sizeof(SOURCE) / sizeof(SOURCE[0])); i++) {\
                             ^
main.c:21:22: note: declared here
void print_array(int int_nums[5]) {
                     ^
main.c:22:3: warning: 'sizeof (int_nums)' will return the size of the pointer, not the array itself [-Wsizeof-pointer-div]
  map(int, int_nums, result, print_int);
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.c:5:39: note: expanded from macro 'map'
  for (int i = 0; i < (sizeof(SOURCE) / sizeof(SOURCE[0])); i++) {\
                       ~~~~~~~~~~~~~~ ^
main.c:21:22: note: pointer 'int_nums' declared here
void print_array(int int_nums[5]) {
                     ^
4 warnings generated.

sizeof演算子を使って配列の要素数を求めるときには、その配列変数がどう宣言されているかに気を配る必要がある。また、個人的な意見としては、このような気が狂ったマクロを冗談以外で使うべきではない。冗談としてはかなりイカしてると思う。