floatのストリーム
「floatのストリームってありなの?」というCryoliteさんからの宿題が残っていたので調べてみました。
コードを確信したところ、Hamigaki.Audioでは紆余曲折あって、Sink/Sourceレベルでしか使用できないようになっていました。
なので、Boost.Iostreamsとは無関係にC++のストリームでfloatを扱えるかどうかを試しました。
まず、floatが文字となりうるかです。
17.1.2 character [defns.character]
in clauses 21, 22, and 27, means any object which, when treated sequentially, can represent text. The term
does not only mean char and wchar_t objects, but any value that can be represented by a type that provides
the definitions specified in these clauses.17.1.3 character container type [defns.character.container]
ISO/IEC 14882/2003
a class or a type used to represent a character (17.1.2). It is used for one of the template parameters of the
string and iostream class templates. A character container class shall be a POD (3.9) type.
「can represent text」という箇所が若干引っかかりますが、PODであれば大丈夫でしょう。
次は、char_traitsです。
std名前空間中のテンプレートのfloat等の基本型に対する特別バージョンは定義できないので、別物を用意します。
#include <algorithm> #include <limits> #include <string> namespace hamigaki { namespace audio { // charTはfloat、double、long double template<class charT> struct char_traits { typedef charT char_type; typedef charT int_type; typedef std::streamoff off_type; typedef std::streampos pos_type; typedef std::mbstate_t state_type; static void assign(char_type& c1, const char_type& c2) { c1 = c2; } static bool eq(const char_type& c1, const char_type& c2) { return c1 == c2; } static bool lt(const char_type& c1, const char_type& c2) { return c1 < c2; } static int compare(const char_type* s1, const char_type* s2, size_t n) { return std::lexicographical_compare(s1, s1+n, s2, s2+n); } static std::size_t length(const char_type* s) { std::size_t len = 0; while (*s) ++s; return len; } static const char_type* find(const char_type* s, size_t n, const char_type& a) { return std::find(s, s+n, a); } static char_type* move(char_type* s1, const char_type* s2, size_t n) { if (s1 < s2) std::copy(s2, s2+n, s1); else std::copy_backward(s2, s2+n, s1+n); return s1; } static char_type* copy(char_type* s1, const char_type* s2, size_t n) { std::copy(s2, s2+n, s1); return s1; } static char_type* assign(char_type* s, size_t n, char_type a) { std::fill_n(s, n, a); return s; } static int_type not_eof(const int_type& c) { return (c == c) ? c : int_type(); } static char_type to_char_type(const int_type& c) { return c; } static int_type to_int_type(const char_type& c) { return c; } static bool eq_int_type(const int_type& c1, const int_type& c2) { if (c1 == c2) return true; else return (c1 != c1) && (c2 != c2); } static int_type eof() { return std::numeric_limits<int_type>::quiet_NaN(); } }; }} // End namespaces audio, hamigaki.
int_typeがchar_typeと同じなのがポイントです。
int_typeの条件は、
- char_typeの有効な文字をすべて表現できる (21.1.2/2)
- char_type→int_type→char_typeに変換しても値が変化しない(21.1.1 to_char_type)
なので、char_typeの値域を-1.0〜1.0に限定し、eof()はquiet_NaN()にしました。
二つ目の条件も、二のべき数を乗除する限りは問題ないはずです。
また、eq_int_type()はNaNを考慮したような定義になっており仕様通りに実装できます。
char_traitsさえあればストリームは作れますが、このままだとおもしろくないのでcodecvtも用意します。
#include <algorithm> #include <locale> namespace hamigaki { namespace audio { // 8ビット符号ありフォーマット用 char←→浮動小数点数変換クラス template<class internT, class stateT=std::mbstate_t> class s8_codecvt : public std::codecvt<internT,char,stateT> { public: typedef char externT; typedef internT intern_type; typedef externT extern_type; typedef stateT state_type; explicit s8_codecvt(size_t refs = 0) : std::codecvt<internT,char,stateT>(refs) { } protected: ~s8_codecvt() // virtual { } // [-1.0, 1.0) -> [-128, 127) result do_out(stateT&, const internT* from, const internT* from_end, const internT*& from_next, externT* to, externT* to_limit, externT*& to_next) const // virtual { result res = ok; while (from != from_end) { if (to == to_limit) { res = partial; break; } *(to++) = static_cast<externT>(static_cast<signed char>( *(from++) * static_cast<internT>(128) )); } from_next = from; to_next = to; return res; } // [-128, 127) -> [-1.0, 1.0) result do_in(stateT&, const externT* from, const externT* from_end, const externT*& from_next, internT* to, internT* to_limit, internT*& to_next) const // virtual { result res = ok; while (from != from_end) { if (to == to_limit) { res = partial; break; } *(to++) = static_cast<internT>(static_cast<signed char>(*(from++))) / static_cast<internT>(128); } from_next = from; to_next = to; return res; } result do_unshift(stateT&, externT* to, externT* to_limit, externT*& to_next) const // virtual { to_next = to; return noconv; } int do_encoding() const throw() // virtual { return 1; } bool do_always_noconv() const throw() // virtual { return false; } int do_max_length() const throw() // virtual { return 1; } }; }} // End namespaces audio, hamigaki.
これを使うと、8ビット符号ありリニアPCMデータをファイルから読み込み、floatのストリームとして扱うことができます。
#include <fstream> #include <iostream> #include <iterator> int main() { try { // 適当なPCMデータを作成 { std::ofstream os("tmp.pcm", std::ios_base::binary); for (int i = -128; i < 128; ++i) os.put(static_cast<char>(static_cast<signed char>(i))); } // 作ったデータの読み込みテスト { // floatストリームの作成 typedef hamigaki::audio::char_traits<float> traits_type; std::basic_ifstream< float, traits_type > is("tmp.pcm", std::ios_base::binary); // charとfloatの変換方法を指定 std::locale loc( std::locale(), new hamigaki::audio::s8_codecvt<float>() ); is.imbue(loc); // 表示 std::copy( std::istreambuf_iterator<float,traits_type>(is), std::istreambuf_iterator<float,traits_type>(), std::ostream_iterator<float>(std::cout, "\n") ); } } catch (const std::exception& e) { std::cout << e.what() << std::endl; } }
char_traitsの指定とロケールの変更が必要なこと以外は普通の文字ストリームと変わりません。
ちょっと試した限りでは特に問題なく動いているので、floatのストリームはありなんじゃないかと思います。
Twitterアカウント作りました
とりあえずアカウントだけ。
http://twitter.com/yhamigaki
「y_hamigaki」は偽者なのでご注意を。
Boost.Waveシェル
颯爽と去っていくだけではアレなので、デバッガ上でwaveを動かしてみようと思ったのですが、
何気にwaveにインタラクティブモードがあったので、これで十分な気がしてきました。
C:\Boost>wave.exe -t - Wave: A Standard conformant C++ preprocessor based on the Boost.Wave library Version: 2.0.3.2938 [Win32/Microsoft Visual C++ version 9.0] (20090602) >>> #pragma wave trace(enable) >>> #define HOGE 123 >>> #define STR_I(x) #x >>> #define STR(x) STR_I(x) >>> STR(HOGE) <stdin>:1:1: STR(HOGE) <stdin>:1:9: see macro definition: STR(x) invoked with [ x = HOGE ] [ <stdin>:1:9: see macro definition: HOGE [ 123 rescanning [ 123 ] ] STR_I(123) rescanning [ <stdin>:1:9: see macro definition: STR_I(x) invoked with [ x = 123 ] [ "123" rescanning [ "123" ] ] "123" ] ] "123" >>> ^Z C:\Boost>
「-t」はトレース結果の出力先を指定するオプションで、「-t -」なら標準エラー出力に出力します。
「#pragma wave trace(enable)」でトレースがオンになって、マクロ展開のトレース出力が得られます。
また、「-x」を付けると、pragmaでコマンド置換ができます。
C:\Boost>wave.exe -x Wave: A Standard conformant C++ preprocessor based on the Boost.Wave library Version: 2.0.3.2938 [Win32/Microsoft Visual C++ version 9.0] (20090602) >>> #pragma wave system(dir /B) #line 1 "<stdin>" s8nc s8nc .1 >>> ^Z
シェルっぽいですね。
なお、見えているファイル名はコマンド実行時のリダイレクトに使われる一時ファイルです。
そのままだと権限がなくて一時ファイルを作成できず、以下のように修正した影響です。
Index: trace_macro_expansion.hpp =================================================================== --- trace_macro_expansion.hpp (リビジョン 57536) +++ trace_macro_expansion.hpp (作業コピー) @@ -1091,6 +1091,10 @@ string_type stdout_file(std::tmpnam(0)); string_type stderr_file(std::tmpnam(0)); +#if defined(_MSC_VER) + if (stdout_file[0] == '\\') stdout_file.erase(stdout_file.begin()); + if (stderr_file[0] == '\\') stderr_file.erase(stderr_file.begin()); +#endif string_type system_str(boost::wave::util::impl::as_string(values)); string_type native_cmd(system_str);
VC9のtmpnam()が「\s8nc.1」みたいな文字列を返すのですが、「\」で始まるのに「カレントディレクトリ」の意味らしいので、頭を削っています。
numpunctの補足
numpunctは規格の英文読み違えてました。
debug_numpunct::do_grouping()で64ビット整数用に20グループ分一桁の指定をしていましたが、
最後のグループより後ろは最後の指定に従うため、
std::string do_grouping() const // virtual { return "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1"; }
でなく、
std::string do_grouping() const // virtual { return "\1"; }
としておけばOKでした。
このぐらいの長さであれば、現在主流の短文字列最適化されたstringではコピーの負荷も少なくて済みますね。
ちなみにstd::numpunctでは、
std::string do_grouping() const // virtual { return ""; }
で区切りなしの指定になっています。
続・Boost.Coroutineについての補足
http://d.hatena.ne.jp/melpon/20091212/1260584012
発表者が既に書いてたので、未回答の箇所を補足。
pthreadでマルチスレッドな環境で使えるの?
pthreadが特定の実装を指す言葉でもないので、すべての実装で動くかは保障できないです。
Hamigaki.CoroutineならLinux/NetBSD/Solarisで動作しています。
(Mac OS X/Cygwin版はスレッドでエミュレートしているので除外)
Boost.Coroutineについての補足
昨日のBoost.勉強会でBoost.Coroutineについていくつか質問があったので、補足しておきます。
Hamigaki.Coroutineのコードはすっかり忘れていたので、すぐに答えられなかったのでした。
self.exit()は必要なの?
これはコルーチンの最後の結果を返せるときはreturnで戻ってもOKです。
内部的には
result_type internal_coroutine() { // コルーチンの本体呼び出し result_type res = coroutine_body(); // 最後の結果を返す self.yield(res); // 最後の次はなし self.exit(); }
的なコードになっています。
なお、self_type::exit()には__declspec(noreturn)や__attribute__((noreturn))が付いているので、
対応コンパイラなら「戻り値がない」警告は出ません。
また、Boost.Coroutineを使わない生のコルーチンは元のスレッドと完全に入れ替わっているので、
コルーチン側からreturnすると呼び出し元に戻らずにスレッドが終了してしまいます。
例外を取りこぼしても同じなのでご注意を。
シングルスレッドのプログラムだと即プログラム終了です。
self.exit()が例外を送出する?
self.exit()はexit_exceptionを送出します。これがコルーチンの正常終了です。
呼び出し元へ送出されるのは別の例外coroutine_exitedです。