<cstdint>のリテラルをなんとかしたい【C++】
C++の<cstdint>
は、int32_t
などといった、プラットフォームに依らず、固定長の幅を持つ整数を別名定義しています。このヘッダを用いることで、移植性の高いプログラムを記述できるようになるので、よほどの理由がない限りはint
などを直接使うよりも、これらの型を利用した方がいいのですが、この<cstdint>
にはとある問題があります。
この問題というのが、int32_t
などのリテラルを表現する簡単な方法が用意されていないことです。具体的な例を挙げると、型がint8_t
であるような定数0
を表現するのに、static_cast<std::int8_t>(0)
のように書く必要があります。単に0
と書いてしまうと、これではint
型になってしまうので、明示的なキャストを行う必要があるのです。static_cast
の代わりに関数スタイルのキャストを用いてstd::int8_t(0)
のように書くこともできますが、どちらにせよ毎回定数をこのように書くのは面倒です。
intN_t
であるようなリテラルを簡単に記述できないと困るのは、具体的には次のような場面です。
右辺の値を
auto
で受けたいとき。明示的なキャストをしないと、変数の型がint
として推論されてしまいます。auto x = std::int8_t(0);
std::max
のような、型T
が引数から推論されるテンプレート関数で、複数のT
を引数にとる関数を呼び出すとき。引数の型が一致しないと型推論が行えずエラーになります。std::int64_t y = -1; // int64_tがlongまたはlong longの別名であるとき、std::max(y, 0);はエラー auto z = std::max(y, std::int64_t(0));
整数の型によってオーバーロードされている関数を呼び出したいとき。
void f(std::int32_t x) {} void f(std::uint32_t x) {} void main() { f(std::uint32_t(0)); }
intN_t
のリテラルをもっと簡単に書く方法はないのでしょうか。
幸いにも、C++11以降にはユーザー定義リテラルがあります。次のようにユーザー定義リテラルを定義します:
#include <cstdint>
#ifdef INT8_MAX
constexpr inline std::int8_t operator""_i8(unsigned long long x) {
return static_cast<std::int8_t>(x);
}
#endif
#ifdef UINT8_MAX
constexpr inline std::uint8_t operator""_u8(unsigned long long x) {
return static_cast<std::uint8_t>(x);
}
#endif
#ifdef INT16_MAX
constexpr inline std::int16_t operator""_i16(unsigned long long x) {
return static_cast<std::int16_t>(x);
}
#endif
#ifdef UINT16_MAX
constexpr inline std::uint16_t operator""_u16(unsigned long long x) {
return static_cast<std::uint16_t>(x);
}
#endif
#ifdef INT32_MAX
constexpr inline std::int32_t operator""_i32(unsigned long long x) {
return static_cast<std::int32_t>(x);
}
#endif
#ifdef UINT32_MAX
constexpr inline std::uint32_t operator""_u32(unsigned long long x) {
return static_cast<std::uint32_t>(x);
}
#endif
#ifdef INT64_MAX
constexpr inline std::int64_t operator""_i64(unsigned long long x) {
return static_cast<std::int64_t>(x);
}
#endif
#ifdef UINT64_MAX
constexpr inline std::uint64_t operator""_u64(unsigned long long x) {
return static_cast<std::uint64_t>(x);
}
#endif
constexpr inline std::int_fast8_t operator""_if8(unsigned long long x) {
return static_cast<std::int_fast8_t>(x);
}
constexpr inline std::uint_fast8_t operator""_uf8(unsigned long long x) {
return static_cast<std::uint_fast8_t>(x);
}
constexpr inline std::int_fast16_t operator""_if16(unsigned long long x) {
return static_cast<std::int_fast16_t>(x);
}
constexpr inline std::uint_fast16_t operator""_uf16(unsigned long long x) {
return static_cast<std::uint_fast16_t>(x);
}
constexpr inline std::int_fast32_t operator""_if32(unsigned long long x) {
return static_cast<std::int_fast32_t>(x);
}
constexpr inline std::uint_fast32_t operator""_uf32(unsigned long long x) {
return static_cast<std::uint_fast32_t>(x);
}
constexpr inline std::int_fast64_t operator""_if64(unsigned long long x) {
return static_cast<std::int_fast64_t>(x);
}
constexpr inline std::uint_fast64_t operator""_uf64(unsigned long long x) {
return static_cast<std::uint_fast64_t>(x);
}
constexpr inline std::int_least8_t operator""_il8(unsigned long long x) {
return static_cast<std::int_least8_t>(x);
}
constexpr inline std::uint_least8_t operator""_ul8(unsigned long long x) {
return static_cast<std::uint_least8_t>(x);
}
constexpr inline std::int_least16_t operator""_il16(unsigned long long x) {
return static_cast<std::int_least16_t>(x);
}
constexpr inline std::uint_least16_t operator""_ul16(unsigned long long x) {
return static_cast<std::uint_least16_t>(x);
}
constexpr inline std::int_least32_t operator""_il32(unsigned long long x) {
return static_cast<std::int_least32_t>(x);
}
constexpr inline std::uint_least32_t operator""_ul32(unsigned long long x) {
return static_cast<std::uint_least32_t>(x);
}
constexpr inline std::int_least64_t operator""_il64(unsigned long long x) {
return static_cast<std::int_least64_t>(x);
}
constexpr inline std::uint_least64_t operator""_ul64(unsigned long long x) {
return static_cast<std::uint_least64_t>(x);
}
このようにリテラルを定義することによって、intN_t
のリテラルを0_iN
のように記述できるようになります。int_leastN_t
、int_fastN_t
に関しても同様に0_ilN
、0_ifN
とできます。intN_t
に関しては、N
ビットちょうどの整数がない環境では定義されないので、INTN_MAX
が定義されているかによって#ifdef
しています。
先ほどの例を、これらのリテラルを用いて書き直すとこうなります:
auto x = 0_i8;
auto y = -1_i64;
auto z = std::min(y, 0_i64);
f(0_u32);
余談ですが、<cstdint>
にはINT32_C(n)
といった定数値マクロが用意されています。N3096によると、INTN_C(value)
はint_leastN_t
に対応する(corresponding)整数定数式になる、とあります。しかしながら、これらのマクロはあくまでC向けのようで、auto x = INT8_C(0);
としたときx
がint_least8_t
にならなかったりします(少なくとも手元のAppleClang 16.0.0では、単に末尾にU
やLL
を付与するのみで、キャストは行われない)。