2014年7月26日土曜日

【読書ノート】Effective Modern C++ - Item 4

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 4では推定された型を表示するテクニックが書かれていました。 コンパイルエラーメッセージを利用したアイデアは単純で面白いです。 実行時は、プラットフォーム依存であっても__PRETTY_FUNCTION__ or __FINCSIG__を使うやり方が楽だと思いました。

Item 4: 推定された型を見る方法を知ろう

デバッグや実験、単純な疑問、それらのために型推定された型を知りたいことがある。

IDEを使う

auto x = theAnser;
auto y = &theAnser;

autoのところにカーソルを持っていけば表示されるというやつ。IDEのいいところ。

コンパイラの出力

コンパイルエラーで型の情報を見る方法。 定義の書かれていないこんなクラスを宣言し、実体化させる。

template<typename TD>
class TD; // TDは"Type Displayer"の略

TD<decltype(x)> xType;
TD<decltype(y)> yType;

するとTDは定義が無いのでコンパイルエラーになり、その時xとyの型がエラーメッセージの中に表示される。 TD<int>、TD<const int>を実体化できないという文脈で表示される。

実行時の出力

まず思いつくのはtypidからstd::type_info::nameを取り出す方法。(しかし後述の通りこの方法は誤り)

std::cout << typeid(x).name() << '\n';
std::cout << typeid(y).name() << '\n';

しかし読みやすいものが返されることは保証されていない。
GNUとClangはxの型をi、yの型をPKiと表示する。
iはint、Pはポインタ、Kはconstを意味する。
Microsoftのコンパイラはxの型をint, yの型をint const *と表示する。

template<typename T>
void f(const T& param)
{
    using std::cout;
    cout << "T = " << typeid(T).name() << '\n';
    cout << "param = " << typeid(param).name() << '\n';
}

const auto vw = createVec(); // std::vector<Widget>を返すファクトリ関数
if (!vw.empty()) {
    f(&vw[0]); // &vw[0]の型はconst Widget *
}

// 出力結果 (gcc)
// T = PK6Widget
// param = PK6Widget
// ※6はクラス名の文字数を示す

// 出力結果 (MSVC)
// T = class Widget const *
// param = class Widget const *
// "Widget const *"は"const Widget *"と同じ

Tは合っているが、paramは本来const Widget * const &のはずである。
これは、std::type_info::nameが型を値渡しされた時のように扱うため。
参照とconstが取り除かれるため、const Widget *になってしまう。

また、この場合、残念なことにIDEも解読しづらい結果を返してくる。

// Tの型
const
std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget,
std::allocator<Widget> >::_Alloc>::value_type>::value_type *

// paramの型
const std::_Simple_types<...>::value_type *const &

typedefを追っていけば実際に指している型が分かるが、長い上にparamの方は...と省略されてしまっている。

一方、コンパイラのエラーメッセージは信頼できる。

TD<T> TType;
TD<decltype(param)> paramType;

とすればコンパイル時に正しい型を表示してくれる。

実行時に正しい型情報を表示するため、std::is_constやstd::is_pointer, std::is_lvalue_referenceなどとtypeidを組合せて自作の表示関数を作るのも手だ。
しかし、プラットフォーム依存であることを許せば簡単な方法がある。

template<typename T>
void f(const T& param)
{
    #if defined(__GNUC__)
        std::cout << __PRETTY_FUNCTION__ << '\n'; // GNUとClang用
    #elif defined(_MSC_VER)
        std::cout << __FUNCSIG__ << '\n'; // Microsoft用
    #endif
}
// 出力結果
// GNU
// void f(const T&) [with T = const Widget*]
// Clang
// void f(const Widget *const &)
// Microsoft
// void __cdecl f<const classWidget*>(const class Widget *const &)

Things to Remember

  • 推定された型はIDEのエディタ上か、コンパイルのエラーメッセージか、typeidか、__PRETTY_FUNCTION__や__FUNCSIG__などの言語拡張で見ることができる。
  • しかしそれらの表示は有用でなかったり正確で無かったりするので、C++標準規格の型推定ルールを理解していることは相変わらず重要。

0 件のコメント:

コメントを投稿