こんにちは。田原です。
今回はTheolizer®から離れてC++11の話題です。
C++11でscoped enumと言うバグ検出力を高めたenum型が追加されました。安全なのですが、中身を確認したい時、static_cast<int>()などが必要なのでちょっと面倒です。従来通りstd::cout << enum変数 << “\n”;できるようなツールを作りました。
ご自由に使って下さい。(ただし、私からの保証はありませんので、ご自身の責任でお願いします。)
1.使い方
使い方は簡単です。ostream-enum.hをインクルードするだけです。後はscoped enumも普通に出力できるようになります。
以下、Windows 10のコンマンド・プロンプトにおけるVisual Studio 2015とMinGW 5.4.0でのコンパイルと実行結果です。(両方とも事前にパスを通しています。)
Visual Studio 2015
C:\ostream-enum>cl main.cpp /EHsc Microsoft(R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x64 Copyright (C) Microsoft Corporation. All rights reserved. main.cpp Microsoft (R) Incremental Linker Version 14.00.24215.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:main.exe main.obj C:\ostream-enum>main aNormalEnum=1 aScopedEnum=2
MinGW 5.4.0
C:\ostream-enum>g++ main.cpp -std=c++11 C:\ostream-enum>a.exe aNormalEnum=1 aScopedEnum=2
2.ソース
まず、実装は関数テンプレートなのでヘッダ・オンリーです。
考え方は単純です。
std::ostream& operator<<(std::ostream&, 型)というstd::ostreamに対する出力演算子を、enum型に対してオーバーロードしています。
ただし、「enum型」は例えば「int型」のように特定の1つの型ではありませんので、単純にはオーバーロードできません。関数テンプレートにすれば複数の型に対して定義できますが、関数テンプレートは部分特殊化できません。従って、enum型全般に対して特殊化する(別途定義する)ことがC++言語仕様としてはできないのです。
しかし、世の中には頭の良い人達がいるもので、特定の型群に対して関数テンプレートでオーバーロードすると言う荒業(?)で事実上の部分特殊化する方法が見つかっています。SFINAEとテンプレート・メタ・プログラミングを利用した難しい使い方です。興味のある方は上記のリンク先とQiitaのこの記事が参考になりますので、ご参照下さい。
この原理を利用して、19行目~24行目でtEnumがenum型の時だけオーバーロードしました。
C++11で<type_traits>と言うヘッダが追加されています。これは型に対して色々な操作を行うライブラリです。
今回はこの中から、std::is_enum<T>とstd::underlying_type<T>を使いました。
項目 | 説明 |
---|---|
std::is_enum<T> | Tがenum型ならvalueがtrueになる |
std::underlying_type<T> | Tはenum型でその内部表現の整数型がtypeとして定義される |
std::is_enum<T>::valueは、Tがenum型ならtrueになり、そうでないならfalseとなります。
enum型はその値を保存するために内部的になんらかの整数型を割り当てています。std::underlying_type<T>::typeはその整数型になります。従って、std::underlying_type<T>::type型へのstatic_castは安全に行えます。
>std::ostream& iOStream
ostreamなのに iOStreamなのか(困惑)
・・・osとかいうふうに命名したほうが混乱が無いかも?
コメント、ありがとうございます。
なるほど、確かにそうですね。
実は、仮引数名のプリフィクスとして、入力はi、出力はo、入出力はioを付ける命名規則使ってます。結構優れた規則と思うのですよ。
しかし、指摘されて気が付きましたが、このケース実は悩ましいですね。確かに出力先なのでoを付けるべきな気もしますし、オブジェクトの渡し方としては入力なんですよ。う~む、悩ましいです。
この件、teratailで数名の方の意見を聞いて、自分の意見を整理できました。
結論としては、このiOStreamは、プログラムからの出力ではありますが、関数としての operator<<に対しては入力に当たります。 プリフィクスの i がちょっと違和感を醸し出しますが、また別のルールを定めると複雑になるので現状のままと致します。 思い出したのですが、実はこの部分は昔から少しだけ気になって、目をつぶっていた部分でした。 お陰で自分の見解を整理することができました。ありがとうございます。