LoginSignup
7
4

More than 5 years have passed since last update.

Cマクロによる似非template

Posted at

C言語への感謝の正拳突き 3日目です。
そろそろ実用的なマクロネタも尽き、苦しくなってきました。

概要

実用的でModernなC++のtemplateをみならい、Cマクロを使って似たようなことをやってみようと考える人はそこそこいるようです。
http://stackoverflow.com/questions/10950828/simulation-of-templates-in-c

私も滅多なことではCマクロでテンプレートもどきみたいなことはやらないのですが、
時々使いたくなります。
例をあげると、C++のソースコードを参考にするときです。

C++のテンプレート

参考ソースコードはflatbuffersから持ってきました。
https://github.com/google/flatbuffers

bufferにendian変換してwrite/readするコードですね。
不要なマクロは除去ってます。

flatbuffers/include/flatbuffers/flatbuffers.h
template<typename T> T EndianScalar(T t) {
    // If you're on the few remaining big endian platforms, we make the bold
    // assumption you're also on gcc/clang, and thus have bswap intrinsics:
    if (sizeof(T) == 1) {   // Compile-time if-then's.
      return t;
    } else if (sizeof(T) == 2) {
      auto r = __builtin_bswap16(*reinterpret_cast<uint16_t *>(&t));
      return *reinterpret_cast<T *>(&r);
    } else if (sizeof(T) == 4) {
      auto r = __builtin_bswap32(*reinterpret_cast<uint32_t *>(&t));
      return *reinterpret_cast<T *>(&r);
    } else if (sizeof(T) == 8) {
      auto r = __builtin_bswap64(*reinterpret_cast<uint64_t *>(&t));
      return *reinterpret_cast<T *>(&r);
    } else {
      assert(0);
    }
}
template<typename T> T ReadScalar(const void *p) {
  return EndianScalar(*reinterpret_cast<const T *>(p));
}
template<typename T> void WriteScalar(void *p, T t) {
  *reinterpret_cast<T *>(p) = EndianScalar(t);
}

Cマクロによる似非テンプレート

このくらいのテンプレートであれば、Cマクロ使ってそのまま移植できそうです。

template.h
#include <stdint.h>
#include <assert.h>

#ifndef Def
#define Def(T) \
  T EndianScalar##T(T t) {\
  if (sizeof(T) == 1) { \
    return t; \
  } else if (sizeof(T) == 2) { \
    T r = __builtin_bswap16(*(uint16_t *)(&t)); \
    return *(T *)(&r); \
  } else if (sizeof(T) == 4) { \
    T r = __builtin_bswap32(*(uint32_t *)(&t)); \
    return *(T *)(&r); \
  } else if (sizeof(T) == 8) { \
    T r = __builtin_bswap64(*(uint64_t *)(&t)); \
    return *(T *)(&r); \
  } else { \
    assert(0); \
  } \
} \
T ReadScalar_##T(const void *p) { \
  return EndianScalar##T(*(const T *)(p)); \
} \
void WriteScalar_##T(void *p, T t) { \
  *(T *)(p) = EndianScalar##T(t); \
}

Def(uint8_t)
Def(uint16_t)
Def(uint32_t)
Def(uint64_t)
Def(int8_t)
Def(int16_t)
Def(int32_t)
Def(int64_t)
Def(float)
Def(double)

#undef Def
#endif

Defが連続しちゃうところがあれですけど、Defの引数にCの型を受け取って、それを文字列化して展開しちゃう。
ちょろい、と思ってテストしてみたら、floatとdoubleが思ったとおりに動かない。。
なぜだ、とおもってディスアセンブルして確認してみたところ、ここが腐っていた。

template.h
#ifndef Def
#define Def(T) \
  T EndianScalar##T(T t) {\
  if (sizeof(T) == 1) { \
    return t; \
  } else if (sizeof(T) == 2) { \
    uint16_t r = __builtin_bswap16(*(uint16_t *)(&t)); \
    return *(T *)(&r); \
  } else if (sizeof(T) == 4) { \
    uint32_t r = __builtin_bswap32(*(uint32_t *)(&t)); \
    return *(T *)(&r); \
  } else if (sizeof(T) == 8) { \
    uint64_t r = __builtin_bswap64(*(uint64_t *)(&t)); \
    return *(T *)(&r); \
  } else { \
    assert(0); \
  } \
} \
T ReadScalar_##T(const void *p) { \
  return EndianScalar##T(*(const T *)(p)); \
} \
void WriteScalar_##T(void *p, T t) { \
  *(T *)(p) = EndianScalar##T(t); \
}

ただしくはこんな感じ。
考えなしにC++のautoをマクロTで置き換えちゃってたのが悪かったです。
Cマクロで似非templateする場合、structやmethodの末尾に型名つけたりするので、名称は非常に重要です。

注意点

(1) Cマクロによる似非templateを多用するのはやめよう。
(2) 思ったとおりに展開してるけど動かないなーと思ったらディスアセンブルして確認しましょう。
(3) マクロを使った小手先の共通化や抽象化より、C++のテンプレートのほうが良い場合が多いため、ModernなC++の使用を検討しましょう。

以上

7
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4