codecvt

てなわけで ( http://d.hatena.ne.jp/janus_wel/20100313/1268483725 ) 実際に narrow character と wide character の変換をする code を書いてみた。つっても sample 的なものだとアレなので class としてまとめて使いやすくしたものを。てか codecvt facet てそれ単体だけじゃなくて state を保持する object ( 一般的には mbstate_t 型の object ) も一緒に保持しないと複数回の変換がうまくいかないのな。あとこの保持の仕方も単純に class member として持つだけじゃダメらしいので ( なぜかは未調査 ) static な object を return する member function を用意するとかかなり苦肉の策でやっと動くようになった。

// nwconv.hpp

#ifndef NWCONV_HPP
#define NWCONV_HPP

#include <locale>
#include <string>
#include <vector>
#include <stdexcept>
#include "wexcept.hpp"

namespace util {
    namespace string {
        template<typename inT, typename exT, typename statT>
            class basic_nwconv {
                private:
                    typedef std::codecvt<inT, exT, statT>   codecvt_t;
                    typedef typename codecvt_t::state_type  state_t;
                    typedef typename codecvt_t::intern_type intern_t;
                    typedef typename codecvt_t::extern_type extern_t;

                    std::locale loc;

                    const codecvt_t& get_codecvt(void) {
                        static const codecvt_t& conv = std::use_facet<codecvt_t>(loc);
                        return conv;
                    }
                    state_t& get_in_state(void) {
                        static state_t in_stat;
                        return in_stat;
                    }
                    state_t& get_out_state(void) {
                        static state_t out_stat;
                        return out_stat;
                    }

                public:
                    basic_nwconv(std::locale loc = std::locale("")) : loc(loc) {
                        if (!std::has_facet<codecvt_t>(loc))
                            throw std::logic_error("Specified locale doesn't have codecvt facet.");
                    }

                    // narrow to wide
                    std::basic_string<intern_t> ntow(const std::basic_string<extern_t>& src) {
                        const std::size_t size = src.length();

                        std::vector<intern_t> dst_vctr(size);
                        intern_t* const dst = &dst_vctr[0];

                        const extern_t* dummy;
                        intern_t* next;

                        const extern_t* const s = src.c_str();

                        if (get_codecvt().in(
                                    get_in_state(),
                                    s,   s   + size, dummy,
                                    dst, dst + size, next) == codecvt_t::ok) {
                            return std::wstring(dst, next - dst);
                        }

                        std::string errmsg("failed: ");
                        errmsg.append(src);
                        throw std::logic_error(errmsg);
                    }

                    // wide to narrow
                    std::basic_string<extern_t> wton(const std::basic_string<intern_t>& src) {
                        const std::size_t src_size = src.length();
                        const std::size_t dst_size = get_codecvt().max_length() * src_size;

                        std::vector<extern_t> dst_vctr(dst_size);
                        extern_t* const dst = &dst_vctr[0];

                        const intern_t* dummy;
                        extern_t* next;

                        const intern_t* const s = src.c_str();

                        if (get_codecvt().out(
                                    get_out_state(),
                                    s,   s   + src_size, dummy,
                                    dst, dst + dst_size, next) == codecvt_t::ok) {
                            return std::string(dst, next - dst);
                        }

                        std::wstring errmsg(L"failed: ");
                        errmsg.append(src);
                        throw util::exception::wlogic_error(errmsg);
                    }
            };

        typedef basic_nwconv<wchar_t, char, mbstate_t> nwconv;
    }
}

#endif // NWCONV_HPP

あー wide character で例外をアレコレする機能は標準ではないので以下のような class を定義して使っておりますですよ。

// wexcept.hpp

#ifndef WEXCEPT_HPP
#define WEXCEPT_HPP

#include <exception>
#include <string>

namespace util {
    namespace exception {
        class wexception {
            public:
                virtual ~wexception(void) throw() {}
                virtual const wchar_t* what(void) const throw() = 0;
        };

        class wlogic_error : public wexception {
            protected:
                std::wstring errmsg;

            public:
                wlogic_error(std::wstring errmsg = std::wstring(L"")) throw() : errmsg(errmsg) {}
                virtual ~wlogic_error(void) throw() {}
                virtual const wchar_t* what(void) const throw() {
                    return errmsg.c_str();
                }
        };

        class wruntime_error : public wexception {
            protected:
                std::wstring errmsg;

            public:
                wruntime_error(std::wstring errmsg = std::wstring(L"")) throw() : errmsg(errmsg) {}
                virtual ~wruntime_error(void) throw() {}
                virtual const wchar_t* what(void) const throw() {
                    return errmsg.c_str();
                }
        };
    }
}

#endif // WEXCEPT_HPP

でこいつを以下のように使うと文字列処理で頭を使わずに済むわけで。 command line に指定した文字列の最初の一文字を落として表示する例。

// main.cpp

#include <locale>
#include <iostream>
#include <string>
#include "nwconv.hpp"
#include "wexcept.hpp"

int main(const int argc, const char* const argv[]) {
    try {
        util::string::nwconv conv;

        for (int i = 0; i < argc; ++i) {
            std::wstring arg = conv.ntow(argv[i]);
            arg.erase(0, 1);
            std::cout << conv.wton(arg) << std::endl;
        }
    }
    catch (std::exception& ex) {
        std::cerr << ex.what() << std::endl;
    }
    catch (util::exception::wexception& ex) {
        std::wcerr << ex.what() << std::endl;
    }

    return 0;
}

例によって VC++ 2008 Express Edition & cmd.exe ( cp932 <--> UTF-16LE ) と andLinux g++ 4.3.3 & zsh ( UTF-8 <--> UCS4 ) で日本語でもきちんと動くのは確認。 MinGW g++ 4.4.0 でも compile はできたけど MSYS 付属の bash は C locale しか入ってないので ASCII characters しか試せない。

codecvt をいじってみてちょっと思ったのは定義のされ方がまさに俺が今回やったこと ( 入出力は narrow で、内部での操作は wide で ) を意図してる感じなんだよね。というのも規格で定義されてる template parameter のひとつめとふたつめの形式名がそれぞれ internT ( internal type の略、訳は内部型 ) と externT ( external type の略、訳は外部型 ) だし、変換するための関数が in ( externT -> internT ) と out ( internT -> externT ) だし。なのでこういう使い方は真っ当なのかもしれない。

あと出力時にわざわざ narrow に直さずに wcout 使えばいいじゃないという指摘はまったくその通りなんだけど VC++ & cmd.exe だと wide character が出力されないのでとりあえずこういう形にしてみた。 C language 的に mbstowcs を使って変換したものは wcout でも問題ないんだけど。