Chiharu の日記

絵描き C/C++ プログラマーの日記です。

C++0x 〜auto のその後

先日の日記の続き。
やりたかったことは下記のようなことで、任意の整数型からレジスタ長に合わせた整数型を選択したいというものでした。

const std::uint16_t src = 0;
auto_fast dst = src; // auto_fast = std::uint_fast16_t にしたいけど無理

トラックバック(人生初トラックバック)で、これに対する解決案を提示してもらいました。

autoだけで,似たようなことはできると思います。
ここではオーバーロードを使って実現してみました。

// こういうのを準備する
#include<cstdint>
inline int_fast8_t fast_cast(int8_t n){return static_cast<int_fast8_t>(n);}
inline int_fast16_t fast_cast(int16_t n){return static_cast<int_fast16_t>(n);}
inline int_fast32_t fast_cast(int32_t n){return static_cast<int_fast32_t>(n);}
inline int_fast64_t fast_cast(int64_t n){return static_cast<int_fast64_t>(n);}
inline uint_fast8_t fast_cast(uint8_t n){return static_cast<uint_fast8_t>(n);}
inline uint_fast16_t fast_cast(uint16_t n){return static_cast<uint_fast16_t>(n);}
inline uint_fast32_t fast_cast(uint32_t n){return static_cast<uint_fast32_t>(n);}
inline uint_fast64_t fast_cast(uint64_t n){return static_cast<uint_fast64_t>(n);}
http://d.hatena.ne.jp/RiSK/20110202/1296641021

用意された fast_cast メソッドを使うと、自動的に std::fast_*_t 型にキャストされる、と。

const std::uint16_t src = 0;
auto dst = fast_cast(src); // auto = std::uint_fast16_t になる

これはこれでも良いのですが、単純なオーバーロードで対応してしまうと、暗黙の型変換で他の組み込み型の不正なキャストが成功してしまいます。

const bool src1 = false;
auto dst = fast_cast(src1); // auto = std::int_fast32_t になってしまう
const double src1 = 0;
auto dst = fast_cast(src2); // auto = std::int_fast32_t になってしまう

上記は言語表現上の意味が変わってしまうキャストになるので、可能であれば避けたいところです。fast_cast メソッドを用意するということ自体は現実的でとても良い案だと思います。要件をまとめてブラッシュアップしてみます。

  • cstdint ヘッダで定義されるビット長固定の下記整数型を std::*_fast*_t に変換する。
    • std::int8_t
    • std::int16_t
    • std::int32_t
    • std::uint8_t
    • std::uint16_t
    • std::uint32_t
  • 上記型の typedef 元の整数型も変換対象にする。
  • 上記型の typedef 元を同じくする別名の typedef 型も変換対象にする。
  • 前述の型以外の型は変換対象外とする。(コンパイルエラーを発生させる

template の出番です。

template <class T> struct fast_type { }; // コンパイルエラー
template <> struct fast_type<std::int8_t> {
 typedef std::int_fast8_t type; };
template <> struct fast_type<std::int16_t> {
 typedef std::int_fast16_t type; };
template <> struct fast_type<std::int32_t> {
 typedef std::int_fast32_t type; };
template <> struct fast_type<std::uint8_t> {
 typedef std::uint_fast8_t type; };
template <> struct fast_type<std::uint16_t> {
 typedef std::uint_fast16_t type; };
template <> struct fast_type<std::uint32_t> {
 typedef std::uint_fast32_t type; };

template <class T>
inline typename fast_type<T>::type fast_cast(T iSrc) { return iSrc; }

テンプレートの特殊化で対応することで不正なキャストが抑制され、fast_cast は上記要件を満たすキャスト メソッドになります。

// std::*_t のキャスト
{
 std::int16_t aSrc = 0;
 auto aDst = fast_cast(aSrc); // auto = std::uint_fast16_t になる
}
// typedef 元のキャスト
{
 signed char aSrc = 0;
 auto aDst = fast_cast(aSrc); // auto = std::int_fast8_t になる
}
// typedef 元の別名 typedef 型
{
 typedef unsigned short test;
 const test aSrc = 0;
 const auto aDst = fast_cast(aSrc); // auto = std::int_fast16_t になる
}
// 想定外のキャスト
{
 double aSrc = 0;
 auto aDst = fast_cast(aSrc); // コンパイルエラーになる
}

こんなところでしょうか。