wave.state

まだBoost.Waveで引っ張ります。
Boost.Waveのインタラクティブモードには状態の保存/復帰機能があります。
この機能はcpp.cppのBOOST_WAVE_SERIALIZATIONの値を1に変えてビルドすることで利用できるようになります。
ただし、コンパイルエラーとロード処理のバグがあったので少し手を加えました。

Index: cpplexer/cpp_lex_token.hpp
===================================================================
--- cpplexer/cpp_lex_token.hpp	(リビジョン 58616)
+++ cpplexer/cpp_lex_token.hpp	(作業コピー)
@@ -280,6 +280,8 @@
     template<typename Archive>
     void serialize(Archive &ar, const unsigned int version)
     {
+        if (!data)
+            data = new data_type(0);
         data->serialize(ar, version);
     }
 #endif
Index: util/cpp_include_paths.hpp
===================================================================
--- util/cpp_include_paths.hpp	(リビジョン 58616)
+++ util/cpp_include_paths.hpp	(作業コピー)
@@ -482,7 +482,7 @@
         >::type map_type;
     boost::serialization::stl::load_collection<
         Archive, map_type,
-        boost::serialization::stl::archive_input_unique<Archive, map_type>,
+        boost::serialization::stl::archive_input_map<Archive, map_type>,
         boost::serialization::stl::no_reserve_imp<map_type>
     >(ar, t);
 }


状態を保存しておくファイルは「-s」オプションで指定します。
ファイル名が「-」なら、「wave.state」になります。

C:\Boost>wave.exe -s -

Wave: A Standard conformant C++ preprocessor based on the Boost.Wave library
Version: 2.0.3.2941 [Win32/Microsoft Visual C++ version 9.0] (20090602)
>>> #define HOGE 123
>>> ^Z

C:\Boost>wave.exe -s -

Wave: A Standard conformant C++ preprocessor based on the Boost.Wave library
Version: 2.0.3.2941 [Win32/Microsoft Visual C++ version 9.0] (20090602)
>>> HOGE
123
>>> ^Z

C:\Boost>

マクロの名前で分かるとおり、状態の保存にはBoost.Serializationが利用されています。
元ソースではアーカイブXMLを利用していて遅いので、
BOOST_WAVE_BINARY_SERIALIZATIONを1に、BOOST_WAVE_XML_SERIALIZATIONを0に変えて、
バイナリフォーマットを使うようにした方がよいです。
また、Boost.Waveで使っている文字列がstringではないため、バイナリフォーマットの方が中を覗きやすかったりもします。
よく使うヘッダをインクルードしたwave.stateを用意しておくと便利かもしれません。

wave.cfg

今日もwaveをいじってました。
waveをそのまま起動すると、インクルードディレクトリや事前定義マクロが設定されていないので、
コマンドライン引数で指定する必要があります。
ただ毎回設定すると面倒なので、設定ファイルを使った方がよいです。
既定では、入力ファイルのあるディレクトリから上へ辿って最初に見つかったwave.cfgが使われます。
書式はコマンドライン引数を1行に1つずつ並べて書くだけです。いわゆるレスポンスファイルですね。
VC++9.0風ならこんな感じです。

-D_CPPUNWIND=1
-D_M_IX86=600
-D_WIN32=1
-D_CPPRTTI=1
-D_MSC_EXTENSIONS=1
-D_MSC_VER=1500
-D_MSC_FULL_VER=150030729
-D_MT=1
-D_NATIVE_WCHAR_T_DEFINED=1
-D__BOOL_DEFINED=1
-D_INTEGRAL_MAX_BITS=64
-SC:\Boost\src\svn
-Sc:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\ATLMFC\INCLUDE
-Sc:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\INCLUDE
-SC:\Program Files\\Microsoft SDKs\Windows\v6.0A\include
-S.


ただし、インタラクティブモードの場合には二つ制限があります。

  • 入力ファイルがないので設定ファイルを明示的に指定する必要がある
  • マクロ定義がクリアされる

二つ目は次のパッチで改善できます。

Index: cpp.cpp
===================================================================
--- cpp.cpp	(リビジョン 57536)
+++ cpp.cpp	(作業コピー)
@@ -996,7 +996,7 @@
         if (is_interactive) {
         // if interactive we don't warn for missing endif's etc.
             ctx.set_language(
-                boost::wave::enable_single_line(ctx.get_language()));
+                boost::wave::enable_single_line(ctx.get_language()), false);
         }
         
     // analyze the input file

ちゃんと調べてないので、何か挙動が怪しくなるかもしれません。

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]
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.

ISO/IEC 14882/2003

「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のストリームはありなんじゃないかと思います。

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 "";
}

で区切りなしの指定になっています。

lexical_castと大域ロケール

lexical_castが流行っているみたいなので、飛行機の中でコードを読んでました。
素朴な実装だとstringstreamに挿入(<<)して抽出(>>)するだけですが、
既定でstringstreamに大域ロケールが吹き込まれるため大域ロケールの影響を受けて出力が変わってしまいます。
数値の入出力に影響する特性項目(facet)にはnum_get、num_put、numpunctがありますが、
Boostの実装では一部の型に対して高速な実装を用意しているため、numpunctしか効きません。
ダミーfacetを使って実際に試してみました。

続きを読む