2008-01-31

俊寛僧都

 平家物語の俊寛は、とても不幸である。平家に謀反を企てたかどで、成経と康頼とともに、鬼界ヶ島へ島流しにあう。成経と康頼は信心深くて、千本の卒塔婆を作って海に流したり、鬼界ヶ島のあちこちを、都人が信仰する場所とみたてて祈ったりしていたが、俊寛は不信心の人だったので、祈らなかった。

 しばらくして赦し文がでたものの、一緒に流された成経と康頼だけが帰ることを許され、俊寛は一人残る。その後、家来であった有王が訪れ、やせ衰え、骨の節々が目立ち、餓鬼のようになった俊寛に会う。息子や北の方も亡くなり、娘は無邪気に、早く帰って来いなどと手紙を書いたことを知り、「生きているからこそつらいのだ」という真理を悟って、自殺する。

 平家物語のこの話を読んだとき、どうも違和感があった。俊寛は本当にそんな奴だったのだろうか。流罪になったからといって出家するような、現金な康頼と違い、俊寛は元から坊主である。世捨て人であれば、流罪になろうが死のうが、どうでもいいはずだ。

 そこへきて、芥川龍之介の、俊寛は面白い。非常にポジティブで、人間的な俊寛を描いている。これでこそ俊寛だ。

 だいたい、平家物語はちょっと考えればおかしいのだ。まず、流されたのが治承元年(1177年)である。  翌年の治承二年(1178年)には、赦し文がでて、少将と平判官は都に帰っている。有王が訪ねるのが、その次の年の治承三年(1179年)である。

 まず、千本の卒塔婆がありえない。島流しにされてから、赦し文が出るまでの間に、千本も卒塔婆を作って流したということである。この間、わずか一年に満たない。成経と康頼の二人だけで、わずかの間に千本も卒塔婆を作るのは、並大抵のことではない。だいたい、加工しやすい材木なんてないのだ。その辺に生えている木を切り倒して、卒塔婆の形に削らなければならぬ。

 また、そもそも鬼界ヶ島はどこかということについては諸説あるが、いずれも薩南諸島あたりなのだ。私は、言葉の通じぬ土人がいるという下りから、喜界島ではないかと思う。言葉が通じないといえば、当時の琉球人だろう。いずれにせよ、台風こそくるものの、一年中暖かい土地だ。都の風景はないにしても、景色も悪くない。

 そして、流されてから三年後の治承三年(1179年)に有王が訪ねたとき、俊寛はガリガリに痩せており、有王をして、我餓鬼道へ来るか、と思わせたほどだと書いてある。

 しかし、少なくとも二年間は、少将の親戚からの仕送りがあったはずだし、少将が帰るので仕送りをやめたとしても、たったの一年足らずで、そこまで劇的に変わるだろうか。

 やっぱり、芥川龍之介の俊寛も、いい線いってるんじゃない? 有王から都の話を聞いて、悲観して自殺に望んだとしても、言われているほど不幸だったとは、どうも思えない。

 まあ、物語は美談を好むわけで、真実かどうかは関係なく、こうでこそありたけれ、という思いが、こういう思考をさせるのかもしれない。

 例えば、勧進帳の富樫のように。富樫だって、最初は、まんまと騙されたマヌケとしか認識されていなかった。だいたい、名乗りが「加賀の国の住人」だけしかないところで、あまり意味はない人間だ。当時の人の美談は、か弱い義経を、周りの主従が助けるところにあったと思われる。ところが、今ではまるで違い、「富樫は義経と見抜きつつも、弁慶の忠義に感動して、敢えて通した」ことが美談となっている。

2008-01-30

更新料についての判例が出ました

http://www.jiji.com/jc/c?g=soc_30&k=2008013000066  曰く、「更新料は賃料の一部の前払いに当たる」  なるほど、前払いという扱いになるらしい。すると、更新期間内に賃貸契約をやめる場合(更新期間が切れたから引っ越すという人は少なく、大抵の人が更新期間内に事情で引っ越すはず)、更新料は返還されなければならないはず。面白い判例だ。覚えておこう。

広く世間に広まるのは難しい

 YouTubeができてから、似たような動画サイトが次々とできた。そういう後追いサイトは、例えばより動画の画質がいいだとか、長時間の動画でも受け付けるだとか、YouTubeの機能をそっくりコピーした上で、さらに追加要素を加えていた。しかし、皆YouTubeに勝ることはできていない。いくらYouTubeが先駆けたとはいえ、YouTubeより優れている付加機能のある、後追いサイトが、なぜ流行らないのか。  そもそも、YouTube以前には、誰も、簡単に動画をアップロードできるWebサイトなどやろうとはしなかった。実現するだけの技術と費用を持っている所は多いはずなのに。  日本には、ニコニコ動画というものがある。これは、YouTubeのように、誰でも動画をアップロードできるサービスで、さらに、動画の上にコメントを流すことができる。そんなことをしたら、動画が見れないではないか。しかし、これがかなり流行った。その後、動画にコメントを流すような機能をつけたWebサイトはいくつかでたが、皆、ニコニコ動画に勝てないでいる。ニコニコ動画よりも高画質、高機能であるにもかかわらず。  実際、zoomeというサービスを見ると、方向性を間違っているとしか思えない。zoomeでは、テロップという名称で、動画上に文字列を動かすことができる。しかし、デフォルトで吹き出しがついていたりする。なぜそんな吹き出しが必要なのか理解できない。また、複雑なコマンドを駆使することができるが、大抵の人は、そんなコマンドを使いこなしたいとは思わない。ニコニコ動画だって、コメ職人と呼ばれている人は、そう多くない。大抵の人は、ただコメントを流せば、それでいいと思っている。zoomeは、そういうコメントで極限まで遊ぶ人には面白いかもしれないが、そうでない大多数の人には、複雑すぎて面白くない。  さらにSNSだ。ややこしい人間関係が生じるところで、誰が、人とは変わった、独創的なコメントなどしたがるだろう。SNSのような匿名性のない人間関係が深い仕組みの元では、協調性のない奇抜なユーザは白眼視され、つまはじきにされるだけだというのに。  まあしかし、動画をアップロードする側からみると、ニコニコ動画は貧弱すぎるというのも事実だ。H.264とAACが使えないのは痛すぎる。

2008-01-28

Chaserのユーモアが分からないアホども

【ニコニコ動画】【国営放送】研究目的で日本人ぶっ殺していい?^^【オーストラリア】

 おいおい、Chaserも知らんのか。このすばらしいブラックユーモアが理解できないとは、いつから日本人はドイツ人になったんだ?  後字幕、後半、"You got a harpoon experience"と聞こえるのだが、字幕が変だ。

Arjun Bijanki:インテリセンスのセンスをインテリにする

Channel 9: Arjun Bijanki: Making Sense of VC Intellisense Channel 9へのリンク

 この動画はなかなか見ごたえがある。

2008-01-27

テンプレート関数で、constを引数に使った場合

 某C++スレで、こんな話がでた。アクセス規制がかかっているので、ここにメモと、正しい答えを書いておく。  質問者は、次のコードの出力に納得ができないでいる。

template < typename T > void f( T ) { std::cout << boost::is_const< T >::value ; } int main() { int x = 0 ; f( x ) ; // 0が出力される int const y = 0 ; f( y ) ; // 0が出力される f<int const>( 0 ) ; // 1が出力される }

 なるほど、確かに、一見すると不思議な結果だ。二つ目の関数呼び出しの引数の型は、int constではないのだろうか。その疑問、もっともだ。ちなみに、このコードは、規格では、001と出力されるのが正しい。  いったいどういう仕組みなのか。まず、関数の引数における、関数の引数のtop-level qualifierは、関数の型に影響を与えない。つまり、constとvolatileで修飾しても、関数の型として見た場合、引数は修飾されていないのだ。

int main() { typedef void func_type( int const volatile) ; std::cout << typeid( func_type ).name() << std::endl ; std::cout << typeid ( boost::function_traits< func_type >::arg1_type ).name() << std::endl ; }

 一行目は、void (int const volatile)という型の関数をtypedefしている。  二行目は、人間が読める形で型を出力する。これは処理系依存だが、VC9の場合、void __cdecl(int) と出力される。__cdeclとはVC特有の呼び出し規約のことで、ここでは関係がない。注目すべきは引数の型だ。たんなるint型になっている。  三行目は、Boostのfunction_traitsというメタ関数を使い、引数の型を調べている。これも、int と出力される。もちろん、function_traitsがcv_qualifierを取り除いているわけではない。ただ規格通りなだけだ。  ここまで分かったら、同じように、f<int> と、f<int const> とを、typeidで比較してみるとよい。どちらも同じ文字列を出力するはずだ。(しかし、ひょっとしたら、ソースコードでの型を出力する処理系があるかもしれない。規格では、関数の型は修飾を省くので、それは正しい型ではないと思うのだけれど)

 よろしい。それは分かった。しかし、もうひとつ解せないことがある。それは、テンプレート引数を明示的に指定した場合は、なぜか、関数内では、引数の型は、cv_qualifierがついているということだ。コレはなぜか。それには、C++の規格にあたらなければならない。14.8.2の3に、次のように書いてある。

[Note: A top-level qualifier in a function parameter declaration does not affect the function type but still affects the type of the function parameter variable within the function. —end note]

 そして、例として示されているコードは、まさに今論じている問題のことだ。つまり、関数の型としては、cv_qualifierがつかないが、その関数内では、引数の型には、cv_qualifierがつく。以上。

 追記  さらに、なぜ、int constな型を引数として渡しているのに、int型にdeductionが働くのかという疑問に対しては、14.8.2.1に、次のように書いてある。

1 Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. 2 If P is not a reference type: (中略) — If A is a cv-qualified type, the top level cv-qualifiers of A’s type are ignored for type deduction. If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction.

 つまり、トップレベルのcv_qualifierは無視されるということだ。

紛らわしい天皇

 白河天皇と後白河天皇、鳥羽天皇と後鳥羽天皇が非常に紛らわしい。  しかも、運悪く、平家物語の前後の天皇だ。  名もなき下郎じゃないんだから、もう少し凝った名前をつけようよ。

2008-01-26

libstdc++を使って作られたプログラムはGPLか

libstdc++は、gccのSTLの実装のことである。これは、GPLライセンスだ。しかし、STLは、その名が示すとおり、テンプレートライブラリであって、ソースコードの形で使わなければならない。したがって、gccで作られたプログラムは、GPLライセンスを継承しなくてはならないのだろうか。

 gccは優れたコンパイラであり、何よりフリーだ。フリーなライセンスとして、GPLがすばらしいことjも確かだ。しかし、gccで生成されたプログラムが、皆GPLになってしまうというのでは、誰も使わないだろう。コンパイラが生成したプログラムまでGPLになるというのは、GPLなテキストエディタで作成したテキストファイルがGPLに感染するようなものだ。

 幸いなことに、どうやらlibstdc++は、特別に、GPLに加えてruntime exceptionという条項があるらしい。

http://gcc.gnu.org/onlinedocs/libstdc++/17_intro/license.html

 しかし、それって別にGPLである必要がないような。libstdc++に改変を加えて使用した場合も、このruntime exception条項は適用できるのだろうか。

ばかげたコーディング規約

213 名前:デフォルトの名無しさん[sage] 投稿日:2008/01/26(土) 13:35:49 前に関ったプロジェクトは常にフル指定?でメソッドを呼び出すべし という規約があった 基底クラスのメソッドさえも Hoge::Foo:Base:Dosomething(); 多重継承を多用していたからという事情もあるんだけど 面倒だけど、安全ではある
 ほほー、fully qualifiedで関数を呼び出す規約ですか。そのプロジェクトでは、ハローワールドは次のようになるんですね。
//これはダメ ADLを使っているから。 std::cout << "Hello,world\n" ; //これはコーディング規約通りの問題ないコード std::operator << (std::cout, "Hello,world\n") ;
 もちろん、メソッドとは、一般に言えば、単にメンバ関数だけをさすので、普通の関数は含まれないのかも知れぬ。しかしC++にはない用語だ。  しかし、嫌なコーディングスタイルだ。大体、Hoge::Foo:Base:Dosomething()とは、三階層下のメンバ関数ということになる。そんな深いメンバ関数を呼び出す必要のある設計は、果たして正しいのか。確かに、そういう風にスコープ演算子を多用するのは面倒だ。ということは、そもそもscope resolution operator(::)はそんな使い方を想定していないのではないか。すると、この場合の正しい設計は、継承ではなくて、メンバとして持つことじゃなかろうか。そして、access to menber operator(.)で呼べば、そんな無用のタイプで指を疲れさすこともない。

CSSを使った、即席の縦書きビューワ

 縦書きとは、無論、文字列を右から左へ、垂直に表記するのがことを指す。近年、コンピュータの普及により、左から右へ、水平方向に表記すること多くなり、既に皆なじんでいるが、やはり、縦書きで読みたいこともある。読むだけなのに、縦書きとは少しおかしいが、他に適当な言葉も思い当たらないので、縦書きという。  まず単純に、文字列を縦に表示するコードを書くのは、それほど難しくない。誰でもかける。ところが、大勢に役立つビューワを作るのは、とても難しい。例えば、フォントフェイスをはじめとして、フォントサイズや文字色、背景色、行間、文字間、も変更可能でなければならないし、禁則処理をする、しない。等々、要求は尽きない。  また、たんなるPlain Textのみを表示できればよいというわけではない。例えば、既存のHTML、XHTMLを、レイアウトの崩れはともかく、縦で読みたい需要もあるし、文字コードは、日本語ならば、最低でもJIS、シフトJIS、EUC、Unicode(UTF8とUTF16の最低でもリトルエンディアン)には対応していなければならないし、青空文庫のような、政治的な規格でこそないが、デファクトスタンダードになっているような書式もある。事に、今の日本では、青空文庫を無視して、「さあさ、お立会い、此は縦書き表示可能にして、我が朝に、このソフトウェアありと云われし、優れたるテキストビューワなり」などと謳えば、いい笑いの草となることは、火を見るよりも明らかである。  さて、世にテキストビューワは色々あれども、いずれも我が用を果たさざる。smoopyなどと自称せるソフトウェアを試してみたが、どれもあまりできがよくはない。かといって、おいそれと自分で作る、というわけにもいかぬ。なんとなれば、上記したる要求を満たすには、途方もない労力と時間が必要だ。  そこで、こんな選択肢もあるということを紹介しよう  青空文庫を縦書きで読む場合を考える。青空文庫は、XHTMLでも提供されていて、これには、文字コードにはない特殊な文字が、画像として提供されている。また、青空文庫は、冒頭で、二つ上の階層にある、default.cssを参照している。なるほど、では、ローカルに保存するときも、このように保存してやればいい。二階層上に、次のようなCSSファイルを置くのだ。
body { direction : rtl ; } div.main_text { writing-mode : tb-rl ; font-family : "MS 明朝" ; } h1, h2 { display : none ; } div.bibliographical_information, div.notation_notes { display : none ; }
 これで、縦表示になる。問題は、writing-modeをサポートしているブラウザが少ないことだが。

2008-01-25

平家物語の読書の進捗

 俊寛僧都が死ぬところまで読み終え候つるに、有王が忠義深くて神妙なりと覚え候。  俊寛が、「生きているからこそつらいのだ」ということを観じて死に行く下りは、結構人の心を捉えたと見え、昔からいろいろと同人物が作られている。例えば、芥川龍之介など。 青空文庫 芥川龍之介 俊寛  話の内容を語ってしまうと、読んでいてつまらなくなってしまうので、あえて語らないが、芥川龍之介の俊寛は、かなりポジティブな性格をしている。しかし、この文章を書くためには、源平盛衰記を読んでいなければならないし、仏教にも明るくなければならないだろう。芥川龍之介はあまり読んでいなかったのだが、これを機会に、他の作品も読んでみることにしよう。  しかし、こういう楽しい俊寛を書いたりしているのに、結局ブロバリンを大量服用して自殺するってのも、解せない話だけれど。  ちなみに、源平盛衰記というのは、平家物語を詳しくし、さらに中国や仏教の故事を、恐ろしく大量に盛り込んだものだ。  例えば、冒頭はおなじみの、祇園精舎の鐘声~で始まるが、まず忠盛が殿上人となって、闇討ちのはかりごとが持ち上がったとき、家貞をボディガードにつけるのだけれど、官人達は、庭にかしこまっている家貞を怪しんで、何者ぞ、と「六位をもって云わせければ」の部分が、「頭左中弁師俊朝臣、蔵人判官平時信を召て」と、具体的な役職と名前が出てきていたりする。  木刀と家貞のおかげで闇討ちはなかった、とここですんなり終わらず、中国の故事、鴻門の会が語られるのだけれど、それが、沛公が項羽に先んじたことからはじまり、司馬遷の史記の、あの有名な部分を余すところなく語りだすという次第。  ちなみに、かの徳川光圀は、源平盛衰記こそもっとも正しく歴史を伝えていると考え、多数の平家物語の異本や、吾妻鏡、玉葉、山槐記などといった文献まで参考にして、源平盛衰記との比較研究をさせた。この研究結果は、参考源平盛衰記という本になっている。

ブラック企業

ブラック会社に勤めてるんだが、もう俺は限界かもしれない
ブラック会社に勤めてるんだが、もう俺は限界かもしれない 2
第二部:ブラック会社に勤めてるんだが、もう俺は限界かもしれない
第三部:ブラック会社に勤めてるんだが、もう俺は限界かもしれない
第四部:ブラック会社に勤めてるんだが、もう俺は限界かもしれない
第五部:ブラック会社に勤めてるんだが、もう俺は限界かもしれない
完結:ブラック会社に勤めてるんだが、もう俺は限界かもしれない

 コレはひどい。
 で、そのシャチョーサンとやらは何をしているんだろ。

 で、もうひとつ、
 >マ男くん、今君は、この会社をやめたいって思ってるだろう。その気持ちが分からない事もない。
 >だけど、やめた先に何があるだろう? よく考えてごらん。私は、今はやめるべきではないと思うよ。

 なぜやめない? 別に仕事がプログラマである必要性がない。精神の向上? 馬鹿馬鹿しい。一トンの重りを背負ってフルマラソンするぐらい馬鹿馬鹿しい。

 それでも、まだ自分もプログラマになる一抹の夢をあきらめていなかったりするわけだ。どこか、C++の文法にしか興味がない者を、雇う所はないかな。ないよな。仕事どうしよう。

テレビ業界のコモンセンスは理解できない

 ワンセグ放送は、そもそも暗号化されていない。しかし、市販のPC用ワンセグチューナを、たいてい独自の暗号化を施している。コレはなぜだろう。どうも、アクセス制御こそかけていないものの、「CCIはCopy One Generation」らしい。ふむ、しかし、なぜそんな業界の暗黙的な約束事を守らなければならないのか。 http://d.hatena.ne.jp/eggman/20080124/1201144818 http://mobilehackerz.blogspot.com/2008/01/uot-100.html  すばらしい。特にコレがすばらしい。 http://mobilehackerz.blogspot.com/2008/01/24_23.html  なぜ今までこんな単純なことができなかったのか。やはりこの業界、おかしいんじゃない?  まあ、私はテレビなどといった終わっているメディアは見ないことにしているので、問題は無いのだが。  ほかにも、たとえばうたわれるものなどというアニメのBDが、"1080i"らしい。なぜこの21世紀にインターレスなのだろう。いったい、いまどこに1080iをネイティブ表示できるブラウン管を所有する、物好きがいるのだろうか。この私でさえ、1080pをネイティブ表示できるWUXGAの液晶ディスプレイを持っているというのに。まあ、そもそも私には、1920x1080という解像度自体からして何か違和感があるのだが。まあ、テレビなど見ないし、アニメも見ないので、どうでもいいか。世の中は、使うか抗議するかの二択だと思う人もいるだろうが、ここには、使わないという選択肢が存在する。  DRM? ミドルタワー2台とラップトップPCを1台持っていて、さらにipodがあり、CreativeとVictorのmp3プレイヤーも持っていて、さらに、iRiverやCowonあたりの、AACがまともに再生できて(VictorのAlneoは、同じLC-AACでも再生できたりできなかったり、詳しい説明もない。第一mp4コンテナの拡張子はmp4だ。m4aではない!)、oggやFLACとかも再生できる奴も買おうかと考えており、そもそもクルマには、CDプレイヤーしか備え付けられていないので、PCM 2-channel signed 16bit 44.1kHzに変換してCD-Rに焼かなければならないし、しかもこの環境は私の環境であって、このDRM音楽を共有する親父も似たような環境を持っている場合、どうすればいいの?  そう思えば、世の中にはこんな宗教のような非論理的な業界を熱心に信仰し、毎日テレビのことばかり話している信者もいる。コモンセンスが理解できない。

2008-01-24

日本の携帯は中国と同じレベルの言論統制が敷かれる事と相成りました。

「あまりに急」「検閲では」――携帯フィルタリングに事業者から不満続出 (1/2) モバゲーを運営するDeNAの時価総額は1500億円毀損、携帯フィルタリング導入政策の大きすぎる波紋  掲示板やSNSの要素を持つサイトは一律に粛清されるそうです。これにより、我が日本国の携帯サイトは、中華人民共和国と同じレベルの表現の自由を得ることになります。Congratulations!  ひとつ、間違えてはならないこととしては、責めるべきは携帯キャリアではなく、政府だということだ。携帯キャリアは、所詮商人である。商売をする国の政治と法律に従わなければ、商売ができない。例えば、GoogleやYahooは、フィルタリングをかけたり、危険思想があると思われるユーザのメールを中国共産主義政府に提供しなければ、中国では商売ができないし、Microsoftは、タイムゾーンのハイライトをやめなければ、インドでWindows 95を販売できなかったわけだ。真に責めるべきは言論統制をしている日本政府や中国政府であって、間違っても商売人にあらず。 追記:  ところで、そもそも政府って誰よ? 今の日本国の政府を作っているのは、ほかならぬ人民だよね。

2008-01-22

義務教育はよかった

 義務教育は幸せだったと、最近思う。とにかく、結果が分かりやすかった。とにかく、勉強すれば、テストで点を取れた。暗記すれば、それでよかった。反復練習が実に効果的なので、時間さえかければ、高得点が出せた。  それがどうだ、二十一歳の今になって、そういう分かりやすいモノサシはない。  英語が読める。とはいっても、英語が読めたからといって、何か特別なことはない。英語が読める結果、日本語の参考書には皆無の、テンプレートメタプログラミングを学べたり、Boostも躊躇せずに使えるし、何人かの有名なMS社員のブログなどを読んで、Windowsへの理解を深めたりできる。もちろん、それだけではない。例えばかの有名なアニメ、サウスパークで大爆笑できたり、doom9に書いてある有益な書き込みを拾い読んでは、動画をより効果的にエンコードすることもできる。  英語が抵抗なく読めることは、低学歴の身としてはありがたいが、これも別に、母国語が英語だったという楽な話ではなくて、六年間も、英語漬けになっていた成果だ。背景は地道な努力に過ぎない。  今は、平家物語を読んでいる。これが小中高ならば、古文のテストは、さだめしいい点が取れたに違いない。しかし、いまはそんな分かりやすい評価は存在しない。ただ読んで、教養を深めるしかない。例えば、「祇王と仏御前は萌え~俺の嫁だろwwwJK」とか、「西光法師ってハードボイルドな奴」とか、「重盛SUGEEEE」だとか、そういうことが自然に口から出てきたとしても、所詮それだけのことだ。誰も評価してくれない。  さて、そんなことより、仕事を見つけなければならないのだが、どうもやる気が出ない。如何せん。  

平家物語 読書を進めて

 近頃、平家物語などを読むようになった。さて、しばらく読み進めて思うに、古文の読解がうまくなったのではないかと思う。たとえば、昔、かの信長公も愛したという、幸若舞の敦盛を読もうと思ったのだが、さっぱり理解できなかったが、現在は、すらすらと読めるようになった。 幸若舞の敦盛 全文  当時は、有名な、人間五十年の下りにしか目が行かなかったが、いまは全文をすらすらと読め、意味が頭に入ってくる。この話、実は平家物語が元ネタなのだ。人間五十年の独白は、熊谷のセリフだ。やれやれ、こんな重要なことも、昔は目にとどまらなかったのか。無教養はつらいものだ。  あらましは、熊谷は、平軍の船の乗って逃げようとする敦盛を挑発して、引き返させ、組み伏せて、討ち取る前に名を聞く。まだ十六歳という敦盛はひとこと、「名乗らずとも、首を見せれば誰だか知れる」と。熊谷は自分の息子と敦盛を重ね、命を助けてやろうと思うが、東軍が後ろに迫り、助けてやれなくなる。泣く泣く首を討ち取った後は、命のはかなさを思い知らされ、出家してしまう。  まあ、実際のところ、化粧までしている、オカマみたいな軟弱息子の敦盛が、歴戦の勇士、熊谷に太刀打ちできるわけもなし。そもそも熊谷だって、単に無骨なだけだっただろう。出家したのも、親戚との領地争いが発端だったんじゃねーの? という話もあるし、まあ、真相はそれほど美談というわけでもないのだろうけど。

2008-01-20

Boostのenable_ifについて

Boostには、enable_ifというメタ関数がある。このメタ関数の実装は、実はとても短い。とても短いので、分かりやすい。

template < bool B, class T = void >
struct enable_if_c
{
  typedef T type;
} ;

tempate < class T >
struct enable_if_c< false, T > {} ;

template < class Cond, class T = void > 
struct enable_if : public enable_if_c< Cond::value, T > {} ;

きわめてシンプルだ。なお、これの逆をする、disable_ifなるメタ関数もある。まず、語るよりも、例を示そうと思う。そのほうが分かりやすいだろう。

例えば、ある関数の呼び出しを、組み込みの整数型に限りたい場合は、どうすればいいだろう。C++では、関数のオーバーロードをサポートしている。

int f(int x) ;
unsigned int f(unsigned int x) ;
short f(short x) ;
unsigned short f(unsinged short x) ;
//以下略

何て面倒なんだろう。それぞれの関数ごとに、同じようなコードを何度も何度も書かなければならない。そこで、テンプレート関数というものがある。テンプレートを使えば、このような無駄な記述は省ける。

template < typaname T >
T f(T x)
{
  return x << 5 ; //ビット演算を使う。
}

これは、すばらしい。しかし、もしこの関数を整数型の呼び出しに限定したい場合は、どうすればいいのだろう。そこで、enable_ifが役に立つ。

template < typaname T >
T f( T x, typename boost::enable_if< boost::is_arithmetic<T> >::type * = 0 )
{
  return x << 5 ; //ビット演算を使う。
}

enable_ifは、一つ目の型引数のメタ関数の戻り値が真の場合、二つ目の型引数を返す。もし偽であったならば、型は定義されない。しかし、C++の規格では、コンパイルエラーにはならない。なぜならば、C++にはSFINAE(Substitution Failure Is Not An Error)という規格がある。このため、単に関数が、Overload Resolutionの候補から外れるだけだ。ほかには、次のような使い方もある。

//戻り値の型として使う
template < typaname T >
typename boost::enable_if< boost::is_arithmetic<T>, T >::type f( T x )
{
  return x << 5 ; //ビット演算を使う。
}

template < typename T, typename Enable = void >
class Foo ;

//整数型にだけ特殊化
template < typename T >
class Foo< T, typename boost::enable_if< boost::is_arithmetic<T> >::type > ;

しかし、依然としてis_arithmeticのようなメタ関数を書かなければならないことに変わりはないし、何の利点があるのか、と思うかもしれない。その場合は、STLのvectorを実装してみるといい。

STLのvectorには、いくつかのコンストラクタがあるが、そのうち、イテレータを引数に取るコンストラクタと、要素数を初期値を引数に取るコンストラクタがある。

void f(int * first, int * last)
{
  std::vector<:int> v(first, last) ; // vectorはイテレータで初期化される
  std::vector<:int> v(10, 123) ; // vectorは要素数10で、初期値が123
}

とても便利だ。さて、早速実装しよう。話を簡単にするために、詳細な実装は省き、コンストラクタだけを定義してみる。

template < typename T >
class vector
{
public :
  vector( unsigned int n, T val = T() )
  {
    T x = val ;
  }

  template < typename Iterator >
  vector( Iterator first, Iterator last )
  {
    T x = *first ;
  }
} ;

さて、さっそくテストしてみよう。ところが、次のコードがコンパイルできないという文句が、大量に殺到して、君のgmailアカウントが容量オーバーになってしまった。

std::vector<int> v(10, 123) ;

なぜか、イテレータを引数にとるコンストラクタが呼ばれてしまう。この理由を説明するには、Overload Resolutionの規格を説明しなければならない。それを説明しだすと長いので、ここでは説明しないが、とにかく、オーバーロードの解決は、テンプレートの実体化が終わった後に行われるということだ。この場合、次の二つの候補がある。

//イテレータ
vector(int first, int last) ;
//要素数と初期値
vector(unsigned int n, int val) ;

なぜこうなるかというと、10とか、123などといったリテラルの型は、int型だからだ。さて、いったいどちらの関数が呼ばれるのが、自然だろうか。要素数と初期値をとるコンストラクタは、int型からunsigned int型への変換が必要だ。すると、変換せずとも呼べるほうがよい。そこで、イテレータ版のコンストラクタが呼び出される。めでたしめでたし。

そう、悪いのはクラスを書いた俺じゃない。ライブラリのユーザの、C++の規格について、理解が浅いのが原因だ。次のように呼び出していれば、イテレータの方は呼び出されないのだ。

std::vector<int> v(10u, 123) ;

注意深く観ると、一つ目の引数の後ろに、uがついている。これは、リテラルがunsigned型であることを明示している。テンプレート関数と、普通の関数が重なった場合、普通の関数が優先されるルールがあるので、これで望みの動作が得られる。ユーザは文句を言う前に、ちゃんと型を考えるべきだったのだ。どっとはらい。

と、ここで話は終わらない。相変わらず、君の二つ目のgmailアカウントは容量オーバーのままだ。ここで必要とされているのは、なんとかテンプレート関数が、Overload Resolutionの候補に挙がるのを、制限する方法だ。ユーザがいちいち、引数の型がsignedかunsignedか考えなければならないのは、苦痛極まりない。そこでdisable_ifの出番だ。

template < typename T >
class vector
{
public :
  vector( unsigned int n, T val = T() )
  {
    T x = val ;
  }

  template < typename Iterator >
  vector( Iterator first, Iterator last
    , boost::disable_if< boost::is_integral<Iterator> >::type * = 0)
  {
    T x = *first ;
  }
} ;

これで、望みどおりの動作が得られる。ユーザは何もする必要がない。いちごさけた

2008-01-19

平曲

 平曲のCDってどこかに売っているのかな。ググって見た限りでは、大型書店にありそうな気がするが。しかし、いま、最初から最後まで、そらで語れる琵琶法師はいるのだろうか。

 関係ないが、今年に入ってから、よいと思った音楽

 これは、どうも数年前の歌らしい。こんな歌を知らなかったとは。

 ひぐらしのOPの歌らしいが、コレはすばらしい。なんだか泣き出しそうな歌だ。

 It's A Burning Desire! It's Burning Me

 この同志は革命的に歌がうまい。我らと思想を同じくする同胞は観るべきだ。

 たまたま、いま平家物語を読んでいるので、ツボにはまった。それだけの話。
 

2008-01-18

BoostのSandBoxを探検する

 Boostのsanboxをあさってみた。まず気になるのは、やはりbigint.hppだ。さっそく使ってみた。なるほど、計算はできる。しかし、ひとつだけ問題がある。動かない。bigintに最も期待されることは何か? 動くことだ。なぜこのコードが動かないんだ。
boost::bigint a("1000000000000000") ; boost::bigint x = a / 10001 ;
 VC9のセキュアなイテレータを使うと例外を投げ、セキュアなイテレータを使わないと、無限ループする。  これは使えない。この手のライブラリこそ、Boostに必要だと思うのだけれど。  次に、type_traitsを見てみた。is_better_conversion.hppが気になるところだ。どうやら、次のように使うらしい。
boost::is_better_conversion< int, float, bool >::value // false boost::is_better_conversion< int, bool, float >::value // true boost::is_better_conversion< short, bool, int >::value // false boost::is_better_conversion< short, int, bool >::value // true
 まあ、面白いメタ関数とは言えるが、何に使うんだろう。そもそも、実装から考えるに、is_better_overload_resolution_matchなどと名づけたほうがいいのではないだろうか。  boost/linear_sort以下のファイル  特別興味深いというわけではないが、ジェネリックなソート集。まあ、この辺は教科書にこそ出てくるものの、現実では、ジェネリックなアルゴリズムがあろうと、使いどころがないだろうが。  個人的に期待したいのは、やはりguiとかreflectionなどだが、現状では完成度が話にならないし。

2008-01-16

BoostのMPLへのいざない part 2

 Boostのチュートリアルでは、STLのiter_swapを実装しているけれど、同じにするのでは能がない。ここでは、STLのcopyのようなものの実装を通して、型計算を学ぶことにしよう 2 型計算 2.1 君に仕事だ  君は、とあるプロジェクトで働くことになった。君に割り当てられた仕事は、copy_and_sumという関数を実装することだ。これは、STLのcopyのように振舞いつつ、コピーする値をすべて加算して、合計を返すという関数だ。次のように使う。
int main() { std::vector<int> src, dest(3) ; src.push_back(1) ; src.push_back(2) ; src.push_back(3) ; int ret = copy_and_sum( src.begin(), src.end(), dest.begin() ) ; std::cout << ret << std::endl ; // 6 std::cout << dest[0] << dest[1] << dest[2] << std::endl ; // 123 }
2.2 早速実装しよう  優秀な君ならば、ものの5分で書けるだろう。ちょろい仕事だ。さて、さっそくにも実装しようとしたが、問題がある。
template < typename InputIterator, typename OutputIterator > T copy_and_sum ( InputIterator first, InputIterator last , OutputIterator result ) ;
 いったい、どうやって戻り値の型、Tを指定すればいいのだろう。引数の型は、イテレータなのだ。値とは、イテレータを参照したときの型だ。しかし君は迷わない。ド素人ならいざ知らず、君はSTLを呼吸している男だ。だからこそ、このプロジェクトにも抜てきされたのだ。君は、すべてのイテレータには、value_typeという型がネストされていることを知っている。ソレを使えばよい。
template < typename InputIterator, typename OutputIterator > typename InputIterator::value_type copy_and_sum ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename InputIterator::value_type value_type ; value_type ret = value_type() ; while ( first != last) { ret += *first ; *result = *first ; ++first ; ++result ; } return ret ; }
 C++を呼吸している優秀な君は、Dependant Nameを型として扱う場合には、typenameをつけなければならないということをも、当然知っている。君はこの美しいまでに完璧なコードをコミットした。 2.3 問題発生  ところが、君の同僚が、次のような文句をつけてきた。以下のコードがコンパイルできないというのだ。
void f( int * first, int * last, int * dest) { int ret = copy_and_sum(first, last, dest) ; }
 これは問題だ。intのポインタ型には、ネストされた型など存在しない。一体どうすればいいのだろう。 2.4 長い長い道のり  優秀なプログラマであり、現在はMS社で働いている、Butler Lampsonは言った。   "We can solve any problem by introducing an extra level of indirection."   「あらゆる問題は、ラッパをかませば解決する」と。  この言葉は、かのAndrew Koenigも、この考えが好きで、彼の著作、RUMINATIONS on C++では、難しい問題をクラスで包み、参照カウントを使って解決している。  int型のポインタにvalue_typeなどというネストされた型を付け加えることはできないが、そのようなテンプレートクラスを作ることはできる。幸いにも、この考え方は実に一般的で、STLには、iterator_traitsというクラスがある。コレを使えば、次のようにコードを書き直すことができる。
template < typename InputIterator, typename OutputIterator > typename std::iterator_traits<InputIterator>::value_type copy_and_sum ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename std::iterator_traits<InputIterator>::value_type value_type ; value_type ret = value_type() ; while ( first != last) { ret += *first ; *result = *first ; ++first ; ++result ; } return ret ; }
 これがいわゆる、ラッパだ。ネストされたvalue_typeを直接使う代わりに、iterator_traitsで包み、差を許容する。iterator_traitsは、部分的特殊化(partial specialization)をつかい、すべてのポインタに対して、value_typeなどを提供している。また、そのほかにも、 2.5 メタ関数という概念  ここでは概念の説明をするが、すこし眠くなりそうな話なので、コーヒーなどをすすりつつ読んでほしい。  ここで、前回のbinaryやiterator_traitsと、普通の関数との間に、何か似たような性質を感じないだろうか。普通の関数は、実行時に、引数とともに呼び出され、値を返す。一方、iterator_traitsは、コンパイル時に、型を引数として渡され、型を返す。binaryでは、コンパイル時の定数として、値を返すことができた。これらの性質から、関数に似ているといえる。このテンプレートクラスのことを、メタ関数(Metafunction)と呼ぶことにしよう。メタ関数、コレは重要な概念である。メタ関数には、次のような特色がある。  ・特殊化(Specialization)    ある型に対して、別定義のクラスを用意することができる。一種の条件分岐だ。とくに、部分的特殊化(partial specialization)をつかえば、すべてのポインタに対する条件分岐をさせることができる。  ・多値を返す(Multiple "return values")    C++では、普通の関数は、ひとつしか値を返せないが、メタ関数は、複数の値を返すことができる。単に、複数のtypedefを作ればいい。  Boostでは、多値を返すメタ関数を、Blob(ある映画由来の造語)として敬遠している。なぜならば、多くの戻り値があるということは、それだけテンプレートの実体化(instantiate)が面倒になり、コンパイルが遅くなるからだ。必要な戻り値だけを定義すべきだ。  また、ポリモーフィズムの観点から言っても、アンチパターンである。そこで、Boostのライブラリでは、メタ関数は次のようになっている。
metafunction-name<type-arguments ...>::type
 メタ関数とは、明示的に呼び出す(invoke)必要がある。呼び出す(invoke)とは、テンプレートを実体化(instanciate)することである。実体化(instanciate)するためには、クラス内部の型や定数を参照しなければならない。
// Point of Declaration template < typename T > struct metafuncion ; template < typename T > struct metafunction { typedef T type ; } ;//Point of Definition void f() { typedef metafunction<int> mf ; // ここでは実体化されない typedef metafunction<mf> mmf ; //ここでも実体化されない typedef mmf::type::type type ; // Point of Instanciation }
 Boostでは、メタ関数とは、ネストされた型typeによって呼び出す(invoke)ことができるものをさす。 2.6 数値メタ関数  メタ関数は、数値を返すこともできる。文法は、次のとおりである。
metafunction-name<type-arguments ...>::type::value
 いくつかのメタ関数は、typeでメタ関数を呼び出さなくても、直接valueを持っている。コレは主に、コードを簡潔にするためだ。 2.7 コンパイル時の選択  さて、仕事に戻ろう。iterator_traitsを使った、copy_and_sumをコミットした君の元へ、ベンチマークを取っている同僚から、バグ票があがってくる。なんと、copy_and_sumが遅いというのだ。話を聞くと、int型の配列で、まず合計値を計算し、つぎにmemcpyを使ってコピーした場合と、copy_and_sum関数を使った場合を比較すると、copy_and_sum関数が1.5倍ほど遅いらしい。  コピーするコードは、この上もなくシンプルなので、これ以上改良しようがない。おそらく、コンパイラベンダーは、memcpyに特殊な最適化を施しているのだろう。しかし、copy_and_sumの実装で、memcpyを使うわけには行かない。なぜならば、memcpyを使えるのは、ビットワイズコピーが許される、POD(Plain old data)型に限られているからだ。  ここで、コンパイル時に、選択をする必要に迫られる。もし、InputIteratorとOutputIteratorの型が同じで、int *型や float *型といった型であれば、memcpyを使ってもよいだろう。それ以外は、依然と同じように処理するのだ。この型を判断するには、テンプレートの特殊化を使い、int型やfloat型などのときだけ、trueを返すようなメタ関数を作ってやればよい。
template < typename T > struct is_it_ok_to_memcpy { static bool const value = false ; } ; template < > struct is_it_ok_to_memcpy< int > { static bool const value = true ; } ; template < > struct is_it_ok_to_memcpy< unsigned int > { static bool const value = true ; } ; template < > struct is_it_ok_to_memcpy< float > { static bool const value = true ; } ; void f() { is_it_ok_to_memcpy< int >::value ; // true is_it_ok_to_memcpy< float >::value ; // true is_it_ok_to_memcpy< std::vector<int> >::value ; // false }
 しかし、必要になるたびに、いちいち作っていくのは面倒だ。幸いにして、Boostには、type_traitsという便利なライブラリがある。コレを使おう。すぐにもこのように書きたいところだが、それはできない。
template < typename InputIterator, typename OutputIterator > typename std::iterator_traits<InputIterator>::value_type copy_and_sum ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename std::iterator_traits<InputIterator>::value_type value_type ; value_type ret = value_type() ; typedef boost::remove_cv<InputIterator>::type itype ; typedef boost::remove_cv<OutputIterator>::type otype ; typedef boost::remove_pointer<InputIterator>::type irptype ; if ( boost::is_same<itype, otype>::value && boost::is_arithmetic< irptype >::value ) { memcpy(result, first, sizeof(irptype) * static_cast<size_t>(last - first) ) ; while ( first != last ) { ret += *first ; ++first ; } } else { while ( first != last ) { ret += *first ; *result = *first ; ++first ; ++result ; } } return ret ; }
 何が悪いのか。このコードでは、if文の両方の分岐がコンパイルされてしまう。普通のイテレータは、void *型に変換できないので、コンパイルエラーとなる。ここでは、片方だけがコンパイルされるような工夫が必要だ。そこで、次のようになる。
template < bool b > struct copy_and_sum_impl { template < typename InputIterator, typename OutputIterator > static typename std::iterator_traits<InputIterator>::value_type do_it ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename std::iterator_traits<InputIterator>::value_type value_type ; value_type ret = value_type() ; while ( first != last ) { ret += *first ; *result = *first ; ++first ; ++result ; } return ret ; } } ; template < > struct copy_and_sum_impl< true > { template < typename InputIterator, typename OutputIterator > static typename std::iterator_traits<InputIterator>::value_type do_it ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename std::iterator_traits<InputIterator>::value_type value_type ; value_type ret = value_type() ; typedef boost::remove_pointer<InputIterator>::type irptype ; memcpy(result, first, sizeof(irptype) * static_cast<size_t>(last - first) ) ; while ( first != last ) { ret += *first ; ++first ; } return ret ; } } ; template < typename InputIterator, typename OutputIterator > typename std::iterator_traits<InputIterator>::value_type copy_and_sum ( InputIterator first, InputIterator last , OutputIterator result ) { typedef boost::remove_cv<InputIterator>::type itype ; typedef boost::remove_cv<OutputIterator>::type otype ; typedef boost::remove_pointer<InputIterator>::type irptype ; bool const b = boost::is_same<itype, otype>::value && boost::is_arithmetic< irptype >::value ; return copy_and_sum_impl< b >::do_it(first, last, result) ; }
 分かりにくい? それならば、これならどうか。
struct oridinary_impl { template < typename InputIterator, typename OutputIterator > static typename std::iterator_traits<InputIterator>::value_type do_it ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename std::iterator_traits<InputIterator>::value_type value_type ; value_type ret = value_type() ; while ( first != last ) { ret += *first ; *result = *first ; ++first ; ++result ; } return ret ; } } ; struct memcpy_impl { template < typename InputIterator, typename OutputIterator > static typename std::iterator_traits<InputIterator>::value_type do_it ( InputIterator first, InputIterator last , OutputIterator result ) { typedef typename std::iterator_traits<InputIterator>::value_type value_type ; value_type ret = value_type() ; typedef boost::remove_pointer<InputIterator>::type irptype ; memcpy(result, first, sizeof(irptype) * static_cast<size_t>(last - first) ) ; while ( first != last ) { ret += *first ; ++first ; } return ret ; } } ; template < typename InputIterator, typename OutputIterator > typename std::iterator_traits<InputIterator>::value_type copy_and_sum ( InputIterator first, InputIterator last , OutputIterator result ) { typedef boost::remove_cv<InputIterator>::type itype ; typedef boost::remove_cv<OutputIterator>::type otype ; typedef boost::remove_pointer<InputIterator>::type irptype ; return boost::mpl::eval_if < boost::mpl::and_< boost::is_same<itype, otype> , boost::is_arithmetic< irptype > > , memcpy_impl , oridinary_impl >::do_it(first, last, result) ; }
 MPLを使えば、このような記述が可能だ。

MPLの続きを書く前に問題がひとつ

 用語をどうしよう。  instanciationは実体化(これもなんだかしっくりこないけど)  metafunctionはメタ関数  では、Higher-order functionは? 数学の素養はないのだけれど、高位関数?  Metafunction forwardingは?  そもそもLazyってどう訳す?  lambdaはlambdaだ、訳しようがない。  基本的なテンプレートは、訳本も出ているから、特殊化とか、実体化などという言葉があるのだろうけれど、この辺の用語はまったく訳されていない。  そこで、あえて訳すという蛮勇の鉈を奮うか、そのまま表記するかになってしまう。いずれにせよ、深く知りたければ、Boostのドキュメントにあたる必要があるので、英語表記のままにしておこう。そのほうが分かりやすいはずだ。  昔、functionに関数という訳語を与えた者を尊敬する。Wikipediaによると、関数と訳したのは中国人らしいのだが。

警察は失業率を基にして刑法犯認知件数にノルマを設定している

http://blog.livedoor.jp/kangaeru2001/archives/51481922.html http://blog.livedoor.jp/dankogai/archives/50986104.html  らしいんですわ。  あまりに理論どおりのきれい過ぎる統計は疑え。そこには人為的な操作がなされているかもしれない。  そもそも、過去四年ぐらいの統計で全体の推移を図れるのか。たとえば、ド田舎の犯罪件数が、去年は3件で、今年が7件だったとしたら、犯罪数が2.3333333333...倍に増加したといえるのか。ひょっとしたら道に落ちている煙草の吸殻に付着した唾液や口内の細胞からDNA鑑定で犯人を特定して検挙したから、犯罪者が増えただけかもしれないのに。  いやあ、しかし、戦前と比べて、現代は実に平和なものだ。なにしろ、一家惨殺事件なんていう、昔ならありふれていて、きりがなかった事件が、テレビで取り上げられるぐらい、減ったのだから。  という考え方もできるわけで。

BoostのMPLへのいざない part 1

 不幸にも(幸いにも?)私はプログラマにならない道を進むわけだけれど、私がC++を学んだ証を残しておきたいと思った。趣味としてのプログラミングは、まあ恵まれている。自分が好きなように書いていいのだから。  世に、Boostなる一群のライブラリがある。中でもMPLは極めてすばらしいのだが、広く世間一般に知られていない。思うに、これはきっかけがないからなのだろう。第一、日本語の文献もほとんど存在しない。ここはひとつ、チュートリアルのようなものを書いてみるべきか。  ここにすばらしい本がある。C++ Templatesと、C++ Template Metaprogrammingである。前者はC++のテンプレート全般について、深く解説している本で、後者は、Boost、とくにMPLに対する本だ。http://www.boost.org/libs/mpl/doc/index.htmlで、その重要な導入部分を読むことができる。この本のすばらしいことは、実に具体的で分かりやすいことだ。翻訳ではないが、この文章を参考に、チュートリアルを書いてみる。なお、このチュートリアルは、具体的なことしか説明しないつもりだ。その背景にあるテンプレートやSTLのコンセプトは説明しないし、個々の文法についていちいち解説するつもりもない。 1. 数値計算 1.1 2進数リテラル  C++には、2進数リテラルが存在しない。なぜ存在しないかの議論はさておくとして、C++の規格にないものはない。あるいは、コンパイラ独自の拡張昨日を使えるかもしれないが、それは移植性の問題を引き起こす。なんとか、C++の規格の中で、2進数を扱う方法はないものか。 1.2 実行時計算  確かに2進数リテラルはない。しかし、2進数としてかかれた10進数リテラルを、10進数に直すことは、特に難しくない。C言語の授業の初日の課題ぐらいにはなるだろう。
unsigned int binary(unsigned int value) { unsigned int ret = 0 ; for (int i = 0; value != 0; ++i ) { ret |= value%10 << i; value /= 10 ; } return ret ; } int main() { std::cout << binary(101010) << std::endl ; // 42 char buf[ binary(101010) ] ; // エラー }
 しかし、実行時に計算するのは、問題がある。C99でないと、配列を確保できないからだ。ここで必要なのは、コンパイル時の定数なのだ。 1.3 コンパイル時計算  そこで、テンプレートの出番だ。
template < unsigned int N > struct binary { static unsigned int const value = binary< N/10 >::value << 1 | N%10 ; } ; template < > struct binary<0> { static unsigned int const value = 0 ; } ; int main() { std::cout << binary<101010>::value << std::endl ; char buf[ binary<101010>::value ] ; }
 テンプレートを、再帰的に呼んでいる。どのように実体化されるかというと、 binary<101010> binary<10101> binary<1010> binary<101> binary<10> binary<1> binary<0>  0になったところで、特殊化が働き、この再帰的なループは終わる。そう、コンパイラに、テンプレートで計算させているわけだ。ループには再帰を、条件分岐には特殊化を使う。再帰に親しいプログラマならば、簡単にこのコードが理解できるはずだ。この手法を用いれば、フィボナッチ数や素数も計算できる。これは、数値に対する計算だ。  さて、とりあえずここまで書いた。この次は型に対する計算を書かなければ。具体的に説明するには、やはりiter_swapのようなものを実装していくのが一番だろう。C++ Template Metaprogrammingは実によくできている。  

2008-01-15

引用権 正当な権利の侵害

 久本雅美という芸人がいる。少し前、この芸人に対するインタビューがテレビで放送された。その動画は、カルト宗教として名高い創価学会の狂信性を裏付けるに足るものであった。そこでこのインタビューの部分、数分が、YouTubeやニコニコ動画をはじめとした、各種動画サイトに上げられた。私は、これが正当な引用であると信じる。  しかし、その動画は、軒並み「権利者の申し立てによる削除」が行われている。不思議だ。法によって保障された正当な引用であるにもかかわらず、なぜ削除されるのだろう。当の動画は、創価学会のそのカルト性を論ずるにあたって、引用するに足る資料で、当然引用は法律によって保障されているはずなのだが。

C++で天啓を得る

 前回、テンプレートメタプログラミングを使いて、アラインメントの問題を直したり。さて今日、暇な午後を満足したりて、半ば眠りかけたるに、たちまちにして天啓を得る。すなわち、「もっと簡単な方法がある」と。  前回のコードはこうだった。
class Bullet { BulletHolder * ptr ; char buf[size] ; //ほかにメンバなど } ;
 コレだから問題なのだ。このようにしてしまえばよい。
class Bullet { struct POD { char buf[size] ;// ポインタのサイズに合わせて適切にアラインメントされているサイズ BulletHolder * ptr ;//何もしなくても、最初から適切にアラインメントされている } data ; //ほかにメンバなど } ;
 このようにすれば、最初から何もする必要がない。気をつけるのは、最初のバッファのサイズだけだ。このバッファの上に、コンパイル時には決定できないアラインメントのオブジェクトが構築されるので、コンパイラには任せられない。ポインタのサイズが4バイトで、バッファの上に構築されるオブジェクトが4バイトアラインメントまでの場合は、4の倍数にしておけばよい。バッファの上に構築されるオブジェクトが8バイトアラインメントまでの場合は、8の倍数に4を足しておけばよい。  ただ、このコードは完璧にアラインメントできるのだろうか。たとえば将来、ポインタのサイズが32バイトになった場合、このコードを32バイトコードにコンパイルしなおしたとき、ポインタのサイズは、まあ4バイトか8バイトだろうと信じている、現在の私のコードは、適切なアラインメントではなくなる。コンパイル時に計算しようにも、取りうる最大のアラインメントの定数をどこかに定義しておかなければ、計算しようがない。  そう考えてみると、Boost.type_traitsの、type_with_alignmentの実装が、なかなか興味深い。結局は総当りになるのか。  

もっとC++で遊ぶことにした

 前回、C++でメモリにやさしい動的多様性を実現しようとしたが、アラインメントの問題を指摘された。  そこで今回は、アラインメントも考慮したコードを書いてみた。
class BulletHolder { private : float x, y ; public : virtual void update() = 0 ; virtual BulletHolder * construct_copy(void * ptr) const = 0 ; virtual ~BulletHolder() { } } ; // BulletHolder *型のあとに構築されるオブジェクトの // アラインメントを合わせるために必要なパディング値を返す template < typename T > struct get_offset : boost::mpl::eval_if_c< sizeof(BulletHolder *) < boost::alignment_of<T>::value , boost::mpl::int_< boost::alignment_of<T>::value - sizeof(BulletHolder *) > , boost::mpl::int_< 0 > > { } ; #define BULLET_MAKE_CONSTRUCT_COPY_PP(TYPE) \ virtual BulletHolder * construct_copy(void * ptr) const \ { return new( static_cast<void *>(static_cast<char *>(ptr) + get_offset<TYPE>::value) ) TYPE(*this) ; } class Aligned_4_Bullet : public BulletHolder { private : boost::aligned_storage<4, 4>::type padding ; // 4バイトアライン public : BULLET_MAKE_CONSTRUCT_COPY_PP(Aligned_4_Bullet) virtual void update() { std::cout << "4 byte aligned " << std::endl ; } virtual ~Aligned_4_Bullet() { } } ; class Aligned_8_Bullet : public BulletHolder { private : boost::aligned_storage<8, 8>::type padding ; // 8バイトアライン public : BULLET_MAKE_CONSTRUCT_COPY_PP(Aligned_8_Bullet) virtual void update() { std::cout << "8 byte aligned" << std::endl ; } virtual ~Aligned_8_Bullet() { } } ; #undef BULLET_MAKE_CONSTRUCT_COPY_PP class Bullet { private : BulletHolder * ptr ; static size_t const size = 32 ; char buf[size] ; //BulletHolderを継承するすべてのクラスが収まるサイズ private : template < typename T > void * getAlignedptr() { return static_cast< void * >( &buf[ get_offset<T>::value ] ) ; } void * getbufptr() { return static_cast< void * >( &buf[ 0 ] ) ; } void safe_destruct() { if ( ptr != 0 ) ptr->~BulletHolder() ; } public : Bullet() : ptr(0) { } template < typename T > Bullet ( T const & x ) { BOOST_STATIC_ASSERT(( boost::is_base_of<BulletHolder, T>::value )) ; BOOST_STATIC_ASSERT(( sizeof(T) + get_offset<T>::value <= size )) ; ptr = new( getAlignedptr<T>() ) T(x) ; } template < typename T > Bullet & operator = ( T const & x ) { BOOST_STATIC_ASSERT(( boost::is_base_of<BulletHolder, T>::value )) ; BOOST_STATIC_ASSERT(( sizeof(T) + get_offset<T>::value <= size )) ; safe_destruct() ; ptr = new( getAlignedptr<T>() ) T(x) ; } Bullet( Bullet const & b ) { if (b.get() != 0) ptr = b.get()->construct_copy( getbufptr() ) ; } Bullet & operator = ( Bullet const & b ) { if ( this != &b && b.get() != 0) { safe_destruct() ; ptr = b.get()->construct_copy( getbufptr() ) ; } return *this ; } BulletHolder * get() const { return ptr ; } BulletHolder * operator *() { return get() ; } BulletHolder * operator ->() { return get() ; } ~Bullet() { safe_destruct() ; } // デバッグ用 void debug() { printf("&buf[0] == %p ptr = %p\n", getbufptr(), ptr ) ; } } ; int main() { Aligned_4_Bullet a4 ; Aligned_8_Bullet a8 ; Bullet b4(a4) ; Bullet b8(a8) ; b4.debug() ; b8.debug() ; }
 重要な部分は、get_offsetメタ関数だ。必要なパディング数を返してくれる。しかもすばらしいことに、このコードを利用する人は、これらの実装を一切気にしなくてもよいということだ。弾を作るにしても、 BulletHolderを継承して、BULLET_MAKE_CONSTRUCT_COPY_PPという、おまじないマクロを使っておけばよい。  上記のコードでは、たとえばポインタのサイズが4バイトだったとして、もし、3バイトアラインメントなどという変態アラインメントがあった場合は、問題なのだが、そもそもpower of 2以外のアラインメントなどありえるのだろうか。  ためしに、Boostのalignment_storageで、3バイトや5バイトのアラインメントを試してみたが、STATIC_ASSERTに引っかかる。Boostがダメと言っているのだから、たぶんそんな変態アラインメントのハードウェアは、存在しないのだろう。  しかし、つくづくテンプレートメタプログラミングは面白いと思う。get_offsetのようなコードを書くと、まさにコンパイラでメタプログラミングしているんだという実感が沸いてくる。

2008-01-14

C++で遊ぶことにした

 弾幕STGをC++で実装する場合について、考えてみた。  弾幕STGには、たくさんの種類の弾が、多数存在するわけだ。  弾はすべて、1フレームごとに状態を更新されなければならないし、当たり判定や、画面外に出たかどうかなどの処理も行わなければならない。  弾ごとにこれらの処理は異なる。  そこで、次のように考えた。  まず、Bulletなるベースクラスを定義して、すべての弾に必要な、座標などのメンバと、必要なメンバ関数を純粋仮想関数で定義する。あとは、弾ごとにこのクラスを継承すればよい。  しかし、ここで問題がある。大量の弾である、クラスのオブジェクトをどうやって管理したらいいのだろう。動的多様性がほしいのだから、すべてBulletクラスのポインタでなければならない。しかし、弾は多数あるので、ひとつひとつnewしていくのはなんだか効率が悪いように思われる。そこで、こんなコードを考えた。
class BulletHolder { private :   float x, y ; public :   virtual void update() = 0 ;   virtual BulletHolder * construct_copy(void * ptr) const = 0 ;   virtual ~BulletHolder() { } } ; #define BULLET_MAKE_CONSTRUCT_COPY_PP(TYPE) \ virtual BulletHolder * construct_copy(void * ptr) const \ { return new(ptr) TYPE(*this) ; } class FastBullet : public BulletHolder { private :   float accel ; //加速度がある public :   BULLET_MAKE_CONSTRUCT_COPY_PP(FastBullet)   virtual void update()   { std::cout << "fast" << std::endl ; }      virtual ~FastBullet() { } } ; class SlowBullet : public BulletHolder { private :   float r ; // 方向がある public :   BULLET_MAKE_CONSTRUCT_COPY_PP(SlowBullet)   virtual void update()   { std::cout << "slow" << std::endl ; }   virtual ~SlowBullet() { } } ; #undef BULLET_MAKE_CONSTRUCT_COPY_PP class Bullet { private :   BulletHolder * ptr ;   static size_t const size = 16 ;   char buf[size] ; //BulletHolderを継承するすべてのクラスが収まるサイズ private :   void * getbufptr()   { return static_cast< void * >( &buf[0] ) ; }   void safe_destruct()   {     if ( ptr != 0 )       ptr->~BulletHolder() ;   } public :   Bullet() : ptr(0) { }   template < typename T >   Bullet ( T const & x )   {     BOOST_STATIC_ASSERT(( boost::is_base_of<BulletHolder, T>::value )) ;     BOOST_STATIC_ASSERT(( sizeof(T) <= size )) ;     ptr = new( getbufptr() ) T(x) ;   }   template < typename T >   Bullet & operator = ( T const & x )   {     BOOST_STATIC_ASSERT(( boost::is_base_of<BulletHolder, T>::value )) ;     BOOST_STATIC_ASSERT(( sizeof(T) <= size )) ;     safe_destruct() ;     ptr = new( getbufptr() ) T(x) ;   }   Bullet( Bullet const & b )   {       if (b.get() != 0)       ptr = b.get()->construct_copy(getbufptr()) ;   }   Bullet & operator = ( Bullet const & b )   {     if ( this != &b && b.get() != 0)     {       safe_destruct() ;       ptr = b.get()->construct_copy(getbufptr()) ;     }     return *this ;   }   BulletHolder * get() const   { return ptr ; }   BulletHolder * operator *()   { return get() ; }   BulletHolder * operator ->()   { return get() ; }   ~Bullet()   { safe_destruct() ; }    } ; int main() {   std::vector<Bullet> v(0) ;   v.push_back(FastBullet()) ;   v.push_back(FastBullet()) ;   v.push_back(SlowBullet()) ;   v.push_back(FastBullet()) ;   v.push_back(SlowBullet()) ;   for (std::vector<Bullet>::iterator iter = v.begin() ;     iter != v.end() ; ++iter )   {     (*iter)->update() ;   } }
 Bulletクラスに、すべての弾クラスが収まるサイズのバッファを用意し、そこにPlacement newでオブジェクトを構築している。  コピーコンストラクタの実装だけが、どうしてもスマートに行かない。  ここまで実装してはたと気づいた。  現実の弾幕STGでは、弾が別の弾を参照することがありえる(たとえばあるランダムに飛ぶ弾を基準に動く弾とか) すると、参照カウンタが必要だ。この場合、Bulletクラスで参照カウンタを実装して、intrusive_ptrにでも渡せばいいのではないだろうか。  そもそも、いくら多数といっても、弾幕STGの弾数は、せいぜい数百程度で、数千も行くことはないだろう。たとえshared_ptrに頼り、さらにSTLのlistを使ったとしても、本当にパフォーマンス上問題があるのか。私のCore2Quadはそんなに遅いのか?  そして、今頃気づいたのだが、上記のコードのコピーコンストラクタをスマートに書くためには、マルチメソッドが必要なのではないだろうか。そうすれば、BulletHolderクラスは、Bulletクラスのことを考えずに、へんなマクロによるおまじないも必要ないのではないだろうか。  と思ったが、マルチメソッドがあったとしても、結局BulletクラスがBulletHolderを継承しているすべてのクラスを網羅する必要があり、あまり違いはないか。

2008-01-13

VC9のよく分からない致命的なエラー

 以下のコードをコンパイルしようとすると、コンパイラ自体が例外的な終了をする。
class Foo { public :   virtual ~Foo() {} ; } ; int main() {   Foo * ptr ;   ptr->~Foo ;//()が抜けている。 }
 念のために行っておくが、コード自体の実行ではない。コンパイルしようとするだけで、VC9のコンパイラは落ちる。  再現させるための問題は二つあると思われる。  1.クラスFooのデストラクタは、仮想関数でなければならない。  2.クラスFooのデストラクタを、ポインタ経由で明示的に呼ぶ。  そもそもコンパイルエラーになるコードなのだが、期待される動作は、以下のコンパイルエラーを出すことだ。 error C3866: destructor call missing argument list  それなのに、実際のエラーは、 fatal error C1001: An internal error has occurred in the compiler.  である。

2008-01-10

Es というゲーム

http://arthearts.net/9th-night/Es/ から体験版を落とすことができる。  公式サイトでは、どのようなゲームかさっぱり分からないが、とても面白いゲームだ。この動画を見れば、その面白さが実感できるはずだ。

 これは製品版が待ち遠しい。しかし、d3dx9_33.dllが同梱されている。やれやれ、同人ゲームの世界ってどこも変わらないな。

どうすりゃいいんだ。

民主党は、ネット上の有害情報を規制すると称して憲法21条を軽んじ、自民党は、暫定ガソリン税を今後10年継続させる法案を用意し、暫定なる語の意を解せざる。 政、苟くも此くの如くんば、且く杯中の物を進めん。

トレーサビリティ(笑)

http://www.mizuho-rice.com/archives/cat10/index.html http://www.mizuho-rice.com/archives/cat8/index.html
 私共、水穂米穀は、お客様の立場に立った日本一の『米屋』=『Rice Shop』を目指しております。 また、米穀店として販売量や売上高等での企業イメージよりも、お客様へ【商品情報を開示】することで、食品としての【安全性】を知っていただくこと、いわゆる【商品イメージ】が大切だと考えました。 「お米」は人の原動力であり、日々の食卓に無くてはならない食材です。 お客様に「食事を楽しく」また「安心」に食べていただく為にも、「お米」に関する豆知識や商品詳細情報(トレサビリティー=精米日毎に生産地・生産者等の情報を開示)を随時公開・更新をしてまいります。
粗悪米を「ひとめぼれ」に偽装、海自に30トン納入  ほほー、証明書を偽装することがトレーサビリティですか。お主もワルよのう。

2008-01-09

今日は実に暖かい日であった

 今日は実に暖かい日であったので、出かけてきた。  まず、古本屋により、平家物語を買った。  次に、ジュンク堂により、本を一冊買った。  最後に、XBox360のコントローラが古くて消耗していたので、新しいものを買いに行った。  平家物語は、父親が文庫本を買ってきたので、読み始めたのだが、いかんせん、文庫本サイズでは読みにくいし、右ページが本文で、左ページが解説というつくりになっていて、連続して読めない。  そこで、少し古そうな古典大全的なものを買ってきた。本が乱雑に積み上げてある、昔ながらの古本屋で探すことしばらく、ようやく見つけ出した。上下巻をレジに持っていくと、一冊千円と言う。千円、それは定価ではないか。もちろん、当時と今では貨幣価値が違へど、定価で売るとはいさぎよい。二千円出して購入した。  あの手の古本屋は必要なのだが、乱雑に詰まれた本だけはいかんともしがたい。書の価値は読めることにあるという、古臭い思想だ。そもそも、値段を直接本に、鉛筆書きするなと声に出して言いたい。保存状態が悪すぎる。第一目的の本を見つけにくい。奥に座っていたオバハンと話していたのだが、やはり思想が古い。やれやれ、BOOKOFFは本の価値を判断せず、昔ながらの古本屋は、本の保存や探しやすい陳列を重視しない。一方を立てれば他方が立たずとは。

2008-01-07

Railsはゲットーのパート2

 久しぶりに見に行ったら、パート2が書かれていた。 http://www.zedshaw.com/rants/rails_is_a_ghetto.html

コンピュータうそつかない

「コンピューターはうそつかないので見逃さないで済む」らしい  なるほど、今までガンが見落とされていたのは、医者の知識や観測機器の精度の問題ではなく、医者が人間として信用できなかったからなのか。すなわち医者はうそをつく。  でも、コンピュータうそつかない。インディアンと同じうそつかない。だから誤診はない。  なるほど、しかし、これらが正しいとするならば、優先すべきはガンのコンピュータ診断ではなく、嘘つき医者の排除だろう。真っ先に排除すべきは、この尼僧みたいな女と、こんな電波を何の疑いもなく発信するNHKだ。

2008-01-05

煙草を吸う奴らはどうしようもない

http://headlines.yahoo.co.jp/hl?a=20080105-00000006-khk-l04  この教師に教わる生徒がかわいそうだ。たばこ規制枠組み条約も知らない者が、義務教育に携わる教師とは。  既にこの条約発効から、三年が経過している。政府は健康増進法を打ち出した。そのため、全国の学校で、構内敷地内全面禁煙が打ち出されるようになってきたのだ。  そして、発行から五年後には、効果的な対策を打ち出さなければならない。2010年2月までには、公共の場では一切禁煙になっていなければならない。  はっきり言う。「分煙」は間違っていると。「全面禁煙」こそが、当然の対策である。第一、どうやって完全に分煙するというのだ。窓やドアなどからは煙が漏れるので、二重のハッチ構造にしなければ煙を完全に防ぐことはできないし、また換気にしても、煙をそのまま外へ出したのでは、意味がない。煙を完全に取り除くフィルターをかけねばならないが、さて、世に分煙を行っていると謳う輩が、コレを徹底しているのだろうか。    いずれにせよ、煙草業界の献金やパーティ券購入、マスメディア規制のせいで、ぐだぐだになっている、この憂うべき我が日本国では、所詮かなわぬ。かかる憂国の事態を打開するには、革命的闘争が必要である。立ち上がれ同胞よ。誓いて退転せず、正当にして当然たる権利として、煙草麻薬指定の立法を勝ち得よ。

2008-01-04

ゲームのプレイ動画は映画か

もともと2chのZoomeスレにレスしたものだったけれど。 ゲームのプレイ動画の著作権はどうなるのか気になっていたが、この機会に調べてみた。 個人的には、ゲームが動画になるためには、あるユーザの操作を伴うので、ゲーム自体の著作権とはまた別になるのではないだろうか。 で、実際にはどうなっているかというと、こんなのを発見した。 ゲームは映画であるかというところが争われたらしいんだけど。 http://civilpro.law.kansai-u.ac.jp/kurita/copyright/commentary/Act26.html というわけで、合法判例はあったが、違法判例もあった。 なんか対立する二つの判例が同じ年にできてしまったようで、 ゲームの内容(アクションかADVか)などで判断されたわけでもなさそうだ。 個人的には、ひぐらしとか、選択肢も何もないので、映画と解釈してもいいだろう。 選択肢はあるが、あまり自由度のない紙芝居ADVとかは曖昧ではないか。 では、東方はどうかあれを映画と解釈するのは無理がある。 とはいっても、もっともっと昔に、パックマンが映画として認められている。 http://www.itmedia.co.jp/news/articles/0401/28/news001.html といっても、この記事では、同一性保持権のほうがよく取り上げられている。 つまりゲームを改造してキャラにエロいセリフを言わせたり、 メッシュやテクスチャを差し替えて、エロくした動画をアップロードしたら、問題かもしれない。 (かもしれない、というのは、著作者が認める場合もあるから) といっても著作権の非親告罪化なんて話も出てきているので、 これからは日本でも、著作物の公開時には明示的に著作権を主張すべきなのかもしれない。 (コピーライト、Creative Commons、パブリックドメインなど) しかし、YouTubeや、ニコニコ動画などの技術は止められないし、 既存のコピーライトやパブリックドメインは極端すぎるというので、 Creative Commonsのような考えもでてきているし(古くはGPLなどもあるけど) 我々の次の世代では、これらの問題なんてどうでもよくなっているかも。 まあ、そのころには、また別の問題があるのだろうが。  追記、  もののついでに知ったのだが、映画の1コマは、映画ではない。これはどういうことかというと、映画のある1コマは、映画に対する特例としての、配給権・頒布権が認められない。

2008-01-03

Railsのコミュニティはゲットーだ

http://www.zedshaw.com/rants/rails_is_a_ghetto.html  所詮人の国なんて、国や文化が違っても、対して変わらない。人の国が嫌ならば、人でなしの国に引っ越すしかあるまい。人の国より住みにくかろうが。  ねえ、seraphy様。  引っ張りだこと言えど(まあ、確かにこの人は引っ張りだこではあるのですが)、現状はこんなものですよ。  何も私は、あなたをリスペクトしていないわけじゃないんですよ。陰口を叩いている訳でもないですし。  あるいは私を、WoWをDDRパッドでやるときだけ運動するような、ドーナッツハンバーガー食ってるだけのピザデブと同じに思っているのかもしれませんがね、私はmakefileを要求しただけなんですがね。これが。

2008-01-02

VFRはいまだ浸透せず

動画というのは、言うなれば画像の連続だ。静止画が連続していることにより、動きとして見えるわけだ。ここでもし、動画のある区間で、動きがない場合、フレームを連続させず、止めていても、人間の目には分からないはずだ。動きがほとんどない場合は、フレームレートを落としても、人間の目には分からない。逆に、動きが激しい場合、この場合は、フレームレートをあげたほうがいいはずだ。

どのような状況にもかかわらず、フレームレートを一定に保つことを、CFR(Constant Frame Rate)といい、フレームレートが可変であることを、VFR(Variable Frame Rate)という。

VFRは理にかなっていて、すばらしいのだが、世の中はいまだにCFRが多い。理由は、VFRにまともに対応しているソフトウェアが少ないからだ。

動画をエンコードする際、入力は既にVFRになっているとしよう。ここで言うVFRとは、ある区間がCFRの30fpsで、ある区間がCFRの60fpsというものではなく、本当に1フレームごとに時間が違っているような、VFR動画のことだ。

H.264にエンコードする場合の、現状はどうだろう。

x264は、動画ストリームをmp4やmkvコンテナに入れて吐くことができるが、VFRにはならない(非公式のパッチはあるにしても)。エンコーダ自体がVFRを取り扱ってくれるのが、理想なのだが、まず現状は、そう恵まれていない。

では、timecodeを出力して、別途、動画ストリームをコンテナにmuxするときに、フレームレートを設定するというのはどうか。mkvコンテナであれば、この方法はとてもうまくいく。最も有名なmkvmergeが、実に分かりやすいフォーマットのtimecodeを受け付けてくれるからだ。しかし、mp4コンテナは恵まれていない。最も有名なmp4boxが、mkvmergeのように分かりやすいtimecodeの入力をサポートしていないからだ。世の中には、現状を打開すべく、tc2mp4なるPerlスクリプトがある。しかし、これは私の理想とするところではない。このスクリプトが何をしているかというと、mkvmergeのtimecodeファイルを、NHMLに変換しているのだ。なぜこんな不便なステップを踏まなければならないのか。

また、AviSynthがVFRに対応していないので、x264にVFRを入力するのも一苦労する。おそらく現状では、フレームを重複させるなどして、一旦CFRに変換し、Decombプラグインで重複するフレームを脱落させてVFRにし(このプラグインはmkvmergeのtimecodeを吐いてくれる) 上記のステップを踏む形になるだろう。なんて無駄が多いのだ。

結局、VFRなH.264のmp4を作る場合、

  1. DirectShowSourceで、VFRを、フレームを重複させることでCFRにする。
  2. Decombというプラグインで重複フレームを削除、mkvmerge用のtimecodeを出力する。
  3. AviSynthの出力を、x264でエンコード。
  4. tc2mp4という、Perlスクリプトを使い、timecodeをNHMLに変換して、mp4boxで適用する。

どう考えても不便極まりない。

さらに絶望的なことに、こうまでしてVFRにしても、劇的な画質向上、ビットレート削減など得られないということだ。

第一、動画を、WUXGA(1920x1200)などの高解像度に拡大してコマ送りで再生し、

「おお、このうp主のエンコードは流石だな。職人芸を感じるぜ」

とか言いつつニヤニヤする奴など、いったい何人いるのだろう。やれやれ、しばらくはVFRをCFRに変換してエンコードしたほうが、楽そうだ。