昨日の京都はそれほど雨が降っていなかったのだが、どうも名古屋のあたりなどは大雨だったらしい。やれやれ。
ともかく、早めに出発するのが吉だろう。
まあ、すこし情報としては遅いけれど、IE8 Beta2がリリースされた。機能などはIE開発チームのブログを読めば分かる。
しかし、遅すぎるんじゃないのかねぇ。このブログのアクセス解析も、日に日にFirefoxが増えている。この間は、とうとうFirefoxの比率が30%を越した。だいたい主要なブラウザがCSS2.1の厳格なサポートは当然として、CSS3に向けて動いている中で、ようやくCSS2.1のまともなサポートときたもんだ。遅すぎる。
白波五人男で最後まで分からなかったセリフの意味が分かった。
弁天小僧「騙りが露れたその時は、送られる氣で新らしく、晒布を一本切つて來たのだ」
渡世人などの伊達を好む男は、腹にサラシを巻くものだ。これは腹を保護する役目もあるのだが、ファッションとしての意味合いも強い。もし、喧嘩沙汰になって重症を負い、サラシを見られるような事態になったとき、そのサラシが汗や垢で汚れていたり、シワだらけであると、みっともない。そこで、ひょっとしたらそういう事態になりそうな時には、見られても恥ずかしくないように、新しいサラシを巻いて出かけるのだ。切るというのは、サラシを作ることだ。当時、サラシというものが売られていたわけではない。売られているのは単なる布である。そこで大きな布切れから、サラシにちょうどいい大きさに布を切るのだ。
一週間ほどの流浪の旅
古くなって使っていず、ほこりをかぶっていたラップトップ
ラップトップのHDDの中に、今回レビュー予定のペーパー
MP3プレイヤー
携帯電話(私は携帯など滅多に使わない人種である)
USB端子に給電できるリチウムイオンバッテリー
充電用のUSBミニBケーブルと、AU携帯独自規格の不思議な端子
ありったけのシャツとパンツ(どうせ足りないから途中で買う)
タオル(別にタオルぐらいどこにでも売っているが)
うむ、持っていくものはほとんど無い。かさばるのはラップトップとシャツぐらいなものだ。
どこで寝るべきか。
カプセルホテルかネットカフェか、最悪屋根があれば満足だからマクドナルドでもいいが、問題は道中立ち寄るであろう岡の旧浅羽町にはそんな洒落たものはない。何しろコンビニに行くまで車を使わなければならないような土田舎なのだから。旧浅羽町をめぐるはそこそこにして、電車やバスがあるうちに浜松に行くべきか。
風呂はどうする。
寝るには野宿でも、この季節死にはしないが、風呂だけはどうにもならん。都合よく銭湯でもあればいいが、最近は少なくなっていると聞くし。
基督教:
それ儀式と称して、大宇宙のユダ公ゾンビの死肉を喰らい、念力なもって、かのゾンビを御主と定むれば、かの御主や必ず、人の子の原罪、すなわち肋骨女が蛇に惑わされて食うたる命の木の実の罪を、許されるであろう。
これ真理なり。
青春18きっぷの購入
東京から京都までの路線を把握
新幹線で東京へ
C++WG会議見学
東京のあたりをうろうろしているはず
森林太郎の墓でも見に行こうか。
静岡に行っているはず
今は無き浅羽町でも見てこようか
名古屋で高校の頃の友人と会う
青春18きっぷがなくなるまで流浪の旅
とりあえずGoogle Mapで機械振興会館の場所を確認。Street Viewが便利だ。
コンセプトの目的のひとつに、テンプレートのエラーを何とか読みやすいものにしようということがある。しかし、ConceptGCCのエラーメッセージはさっぱり分からない。
まず、エラーメッセージを改善しなければならない。具体的には、コンセプトの要求を満たさなかった場合、なんと言う名前のコンセプトの、どの要求を満たしていないのか、エラーで知らせるべきだ。ConceptGCCは何と言うコンセプトを満たしていないとしか言わない。これはとても面倒だ。何しろ、コンセプト自体の記述を間違えても、文法エラー以外は教えてくれないのだから。
よいリファレンスマニュアルが必要だ。STLのすべてのテンプレートなクラスやアルゴリズムに対して、なんと言うコンセプトを満たさなければならないのか明記してあるマニュアルが必要だ。たとえば、std::sortを実装するのに必要なコンセプトを列挙してあるだとかだ。そういうマニュアルがないと、プログラマは仕様書を片手にプログラミングするハメになるだろう。残念なことに、これらの需要を知り、誰かがマニュアルを書いて出版するのは、相当時間がかかると思われる。規格制定から五年はかかるのではないだろうか。それまでは、我々プログラマは規格書を片手にプログラミングしなければならない。
エディタの助けも必要だ。たとえば、Visual Studioのインテリセンスで、あるウインドウに、現在選択している関数テンプレートをConstrained templatesとするのに必要な要求を、すべて列挙するような機能だ。
やはり日本語版ブログはGoogle Readerから削除しておくことにしよう。日本人の気質からなのか、コメントが全然ついていない。コメントのつかないブログほど無意味なものは無い。私のような無名なブログは別として、Windowsの開発チームがやっているというだけで知名度は十分にあるというのに。
知らなかった。求人をしているようだけれど、8月29日締め切りだとか。うーん、その日がちょうどC++標準化委員会の会議で東京に行っている。まあ、応募するだけなら損にもならないだろう。
C++0x いきなりつまづくコンセプト - Faith and Brave - C++で遊ぼう
貧弱ゥ貧弱ゥ!
お前は今までに出したコンパイルエラーの数を覚えているのかッ!
template < typename T >
requires std::Range< T >
&& std::RandomAccessIterator< T::iterator >
&& std::ShuffleIterator< T::iterator >
&& std::OutputIterator< T::iterator, T::iterator::reference >
&& std::OutputIterator< T::iterator, T::iterator::value_type const & >
&& std::CopyConstructible< T::iterator::value_type >
&& std::LessThanComparable< T::iterator::value_type >
void sort( T & r )
{
std::sort( begin(r), end(r) ) ;
}
int main()
{
std::vector<int> v ;
v.push_back(3) ;
v.push_back(1) ;
v.push_back(4) ;
sort(v) ;
for (int item : v)
{
std::cout << item << std::endl ;
}
}
こんな方法もある。
auto concept SortableRange< typename T >
{
requires std::Range< T >
&& std::RandomAccessIterator< T::iterator >
&& std::ShuffleIterator< T::iterator >
&& std::OutputIterator< T::iterator, T::iterator::reference >
&& std::OutputIterator< T::iterator, T::iterator::value_type const & >
&& std::CopyConstructible< T::iterator::value_type >
&& std::LessThanComparable< T::iterator::value_type > ;
}
template < SortableRange T >
void sort( T & r )
{
std::sort( begin(r), end(r) ) ;
}
ちなみに、こうなる理由を説明すると、まず、自分自身はRangeを使っている。std::sortは、次のようになっている。
template<RandomAccessIterator _Iter>
requires OutputIterator<_Iter, _Iter::reference>
&& OutputIterator<_Iter, const _Iter::value_type&>
&& LessThanComparable<_Iter::value_type>
&& ShuffleIterator<_Iter>
&& CopyConstructible<_Iter::value_type>
inline void
sort(_Iter __first, _Iter __last) ;
これも全部必要である。
『コードは動作する』『コンパイルも通る』。両方やらなくっちゃあならないってのが、C++0xプログラマの辛いところだな。覚悟はいいか? オレはできてる。
問われて名乗るもおこがましいが、産まれは遠州浜松在、十四の年に親に放れ、身の生業も白浪の、沖を越えたる夜働き、盗みはすれど非道はせず、人に情を掛川から、金谷をかけて宿々で、義賊と噂高札に、廻る配附の盥越し、危ねえその身の境界も、最早四十に人間の、定めはわずか五十年、六十余州に隠れのねえ、賊徒の張本、日本駄右衛門。
浜松に生まれたならば、遠州弁を話すはずだに。すると、「問はれて名乗るもぶしょったいに。ハァ、俺は浜松に産まれただに」、だとか、「もはや四十になったに。よく人間は五十年って云ふら」、だとか言うはずだに。うーん、かっこよくないに。
昨日、親父が急に、大津歴史博物館と、義仲寺に行こうと言い出した。博物館は、どうせ仏像か何かだろうが、義仲寺には何があるのかと聞くと、芭蕉の墓があるということであった。芭蕉には特に興味が無いので断ろうとすると、木曾義仲の墓があるという。木曾殿と聞いては、流石に、行かないわけにはいくまい。
実は、私は粗野な武人、木曾義仲が好きなのである。当時の特権階級どもが、よってたかって、都の教養と彼らが信じている奇妙な慣習を、木曾殿が持ち合わせていないことを嘲笑するのだが、しかし、現代の客観的な価値観から見ると、木曾殿の方がよっぽど理に適っている。
例えば、かの有名な、猫間の話がある。木曾が左馬頭となって都を守護していたが、あるとき、猫間中納言光隆卿という人が、用事があってやってきた。木曾の郎等の根井という者が、「猫間殿の見参にいり申すべき事ありとて、いらせ給いて候」と取り次ぐと、木曾は爆笑して言った。
「何、猫がきた。猫とは何ぞ。鼠を取るあの猫か。人を猫と呼ぶとは不思議なことだ」
根井も当然そう思って、猫間の雑色にそのことを伝えると、雑色は怒って、「猫間と呼ばれている所に住んでいるから、猫間殿というのだ。木曾殿だって、信濃国木曾におはすから木曾殿というではないか」と言い返したそうだ。なるほど、住んでいるところの地名をもって人を呼ぶのは、確かにこの頃の習わしである。しかし、あまりにも変な地名をそのまま自称するのは馬鹿げているに決まっている。
ともかくも、木曾は猫間と会うのだが、せっかく来たのだから飯でも食って行けと、無塩の平茸というものをだす。無塩とは、塩が必要ないほど新しいという意味である。しかも、その平茸は京都にはないという珍しいもの。ここで、特権階級で何一つ苦労なく、のうのうと暮らしてきた猫間の浅ましさがでてくる。まず猫間は、茶碗が田舎臭いと文句をつける。しかしその茶碗は、木曾が大切にしている仏具の茶碗だったのである。飯が大盛りだとも文句をつけるが、木曾は武人であるので、たくさん食うのは当然である。歌を読んで蹴鞠だけしていればいい特権階級とはわけが違う。
木曾は飯を残さず平らげるが、猫間はまったく口をつけない。さればとて、猫間の雑色に与えると、自分の賎しい身分を棚に上げて、こんな賎しい飯が食えるかと投げ捨てる。無論、木曾の大切にしている仏具の茶碗である。
このあたりの時代は、天下の乱れたこともあって、大飢饉が起こる。後世の人は、木曾の治世がうまくなかったなどと、木曾に責任をなすりつけたりするが、猫間のような贅沢で苦労を知らぬ特権階級ばかりなのだから、いかに木曾を持ってしても、どうにもならなかったというべきである。
他にも、車のすだれを上げさせて乗っただの、車は前から降りるのが作法であるのに、後ろから降りたなどの話もある。心あると自称する人は、これを笑うけれども、考えてみればこれも、木曾が合理的な思考をしていた証拠である。第一、京都は盆地でやたらに暑い。そんなところですだれを閉めて車にのったら、さぞ暑苦しいことだろう。それに、当時は毎日風呂に入る習慣もなかったから、さぞや車内は汗臭かっただろうと思われる。ましてやすだれを閉めたならば、非常に暑苦しい上に汗臭くなることは当然である。車を後ろから降りたというのも、合理的な理由がある。何故ならば、牛は臭い。最近は道端に牛が歩いているということも無いので、土田舎でも無い限り、人は牛の臭いを知らないが、牛と言うのはとても臭いものなのだ。そんな牛に近い車の前からなど、降りたくはないに決まっている。
とにかく、義仲寺に行ってきた。拝観料が三百円かかる。義仲寺はとても狭い寺であった。親父は義仲寺を幕の内弁当と形容した。その故は、幕の内弁当が、狭い弁当箱の中に、様々なオカズを入れるように、義仲寺も狭い敷地内に、墓あり庭ありと、とにかくたくさんのものを詰め込んでいるからであった。池があるのだが小さい上に所狭しと植えられた草木に隠されてほとんど見えなかった。池には亀が泳いでおり、獅子威しまでついていた。
まず第一に見つけたのが、巴御前の墓。巴御前も義仲寺だったのかと驚いたが、考えてみれば、木曾殿と巴御前の墓が一所のあるのは、いかにもありそうなことだ。小さい石に、ところどころ欠けているが、巴と彫ってある。
次に木曽殿の墓。これは墓らしい墓であった。
さて、奥に芭蕉の俳句が彫り付けてある。なるほど、あれが芭蕉の墓なのだろうと近づいてみると、日本の背の高い石碑が建っていた。左には、芭蕉三百年記念碑とある。右には、二百年とあった。なんとも気の長い話だ。
ふと見ると、寺の裏とでもいうべきところに墓がある。親父が近寄って、「おお、この人の墓はこんなところにあったのか」と言っていた。名前は忘れたが、知らない名前であった。親父は、「まあ、一時期それなりに有名だったんだが、今は誰も知らないだろうな」と言っていた。
ふと見ると、木曾を祭っている社があった。木曾も神様となっていたのか。
さて、コムセプトヂィシィシィをだいぶ学びつるに、ひとつ、コムセプトの入門など書かばやと思いたり。さりながら、読み侍る人、いかほどありや。そも、いまだどらふとのままにて、変わることなきにしもあらずと云ふに。
また、所詮コムセプトのその厳格で面倒な仕様をものともせぬ賢人は、自らどらふとを読まんこと疑いはあるべからず。愚かなる人は、たとひ日の本の言葉にて書かれたれど、え読まざればなり。
コンセプトの厳格さと面倒くささを確かめるために、実用的なコードをスクラッチから書いてみた。一時間ほどかかった。ConceptGCCのエラーメッセージは不親切すぎる。
auto concept ValueTypeConcept< typename T >
{
T::T() ;
T::T( T const & ) ;
T::~T() ;
T & operator +=( T &, T const & ) ;
}
auto concept IteratorConcept< typename T >
{
T::T() ;
T::T( T const & ) ;
T::~T() ;
typename value_type = T::value_type ;
T & operator ++ (T & ) ;
value_type operator * ( T & ) ;
bool operator == ( T const &, T const & ) ;
bool operator != ( T const &, T const & ) ;
}
template < typename Iterator >
requires IteratorConcept< Iterator >
&& ValueTypeConcept< Iterator::value_type >
typename Iterator::value_type
accumlate( Iterator first, Iterator last )
{
if ( first == last )
return typename Iterator::value_type() ;
typename Iterator::value_type ret( *first ) ;
++first ;
while( first != last )
{
ret += *first ;
++first ;
}
return ret ;
}
なぜこの程度のコードを書くのに時間がかかったかと言うと、コンセプトの記述を、一箇所間違えてしまったからだ。
auto concept IteratorConcept< typename T >
{
typename value_type = T::value_type ;
value_type operator ++ (T & ) ;
}
こんなミスぐらいすぐに発見できると思うかもしれないが、まさかこんな間違いをするはずが無いという思い込みから、まったく発見できなかった。また、ConceptGCCのエラーメッセージもひどい。STLのint型の要素のvectorのイテレータを渡したら、
test.cpp: In function 'int main()':
test.cpp:72: error: no matching function for call to 'accumlate(__gnu_cxx::__normal_iterator<int*,std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >)'
これだけしかエラーを出力しない。まず、最低限どのコンセプトを満たしていないかぐらい教えてくれよ。それから、あるコンセプトのどの要求を満たしていないのか、ということも教えて欲しい。もしConceptGCCが、
「あんさんあんさん、このコード間違ってまんがな。IteratorConceptっちゅーコンセプトのな、value_type operator ++ (T & ) ; ちゅー要求を、std::vector<int>::iteratorは満たしてへんねやで。もひとつ云うておくとやな、たしかにoperator ++はありまんねん。せやけど戻り値をvalue_typeに変換可能なものはおまへん。そこんとこ気ぃつけて、まひとつよろしく頼んます」
と言ってくれたならば、どんなに楽なことか。このエラーメッセージには、現在のConceptGCCにできないことが三つある。まず一つ目は、どのコンセプトにマッチしていないのか知らせることだ。ConceptGCCは、ちょっと複雑なコードになってくると、とたんにこれができなくなる。二つ目は、typedefされた型名を用いるということだ。libstdc++の内部的な実装の型名を使われても、わけが分からない。MSVCを見習って欲しいものだ。またその際には、テンプレートパラメータのデフォルト引数は省略してくれれば、なおよい。三つ目は、戻り値や引数の型が違う場合に、警告してくれることだ。
Windows Vista Team Blog : Windows Vista: A better investment for your investments
Windows Vista PCs win "hands-down" over Macs for things like better software compatibility, better community support, IE and greater user productivity.
Sure, they really concerns about backward-compatibility, everyone admit it has the biggest community, but... IE?
Wiiのタイガーウッズがでてくるゴルフゲームに、ジーザスのように水の上を歩けるバグがありまして。ソレを受けて、EAがCMを作りました。
これはなかなかよい。
C++0xのコンセプトがいまいちよく理解できなかったので、ConceptGCCのメーリングリストに疑問を投げてみたところ、Douglas Gregor本人が答えてくれた。
ConceptGcc mailing archive page
奴は間違いなく頭がいい。まあ、他ならぬDouglas Gregorなのだから、Conceptに関して詳しいのは当然だが、頭がいい。
associated fucntionのdefault implementationはconstrained templateである。翻訳になってないが、これ以上に翻訳できないのだから仕方がない。
14.9.1.1.10
A default implementation of an associated function is a constrained template (14.10).
よって、次のコードはill-formedである。
auto concept Foo< typename T >
{
void func( T & x )
{ x.func() ; }// ill-formed!
}
なぜならば、デフォルト実装の中でも、当然のごとくコンセプトとして振舞うからだ。然るに、T::func(void)が要求されていない以上、使うことはできない。
auto concept Foo< typename T >
{
void T::func() ;// これでコンパイルできる
void func( T & x ) { x.func() ; }
}
また、コンセプトでは、メンバ変数を要求することはできない。したがって、デフォルト実装の中でメンバ変数を使うことも、また不可能である。このようなメンバ関数を呼び出す非メンバ関数のデフォルト実装ができないと、わざわざすべてのクラスに、非メンバ関数を書くか、concept_mapで定義しなければならないので面倒ではある。
しかし、考えていれば、もしコンセプトが広く使われるようになったならば、プログラマはコンセプトのために、非メンバ関数を用意するのが主流になるから、たぶん大丈夫だろう。
CSS 3 attribute selectors - Opera Developer Community
CSS 3の属性セレクタを簡潔に説明している。
まず、擬似セレクタの、:before と :after これは前後のスタイルを指定するものだ。この擬似セレクタを、generated content と共に使えば、前後に画像を挿入できたりする。
さて、アンカー要素の href 属性に、ある特定の拡張子があったときのみ、特定のスタイルを提供したいという場合、どうすればいいだろう。例えば、画像の拡張子は、bmpやjpgやpngであるし、動画は、aviやmp4やmkvだ。たしかに、URLが拡張子にならない場合もあるが、class 属性を糞真面目につけておくのも面倒である。ではどうするか。
a[ href$='.mov' ]
{
/* mov拡張子へのアンカーに適用するスタイルを記述 */
}
$= というのが、値の最後が指定された文字列で終わる場合のセレクタの記述方法だ。逆に、最初からのマッチは^=で行う。例えば、このブログ内へのリンクに適用するためのセレクタを記述したいなら。
a[ href^='http://cpplover.blogspot.com/' ]
{
/* このブログ内へのアンカーに適用するスタイルを記述 */
}
また、グループ化できる。
a[href$='.rss'], a[href$='.atom']
{
/* RSSかATOMフィードへのアンカーに適用するスタイルを記述 */
}
また、属性の場所を問わず、文字列に部分マッチするには、次のようにする
a[ href*='opera.com' ]
{
/* opera.com へのアンカーすべてに適用するスタイルを記述 */
}
これはおそらく国外にある(B-CASカードを入れた)サーバに地デジの放送信号を転送し、そのサーバでB-CASのスクランブル(MULTI2)を復号化して(コピー制御フラグのついた)MPEG2-TSにしてインターネットで送信しているものと思われる
素晴らしい。噴飯ものだ。フリーオ作っているような小規模なところで、TSを直に流すほど帯域をふんだんに使えるなら、テレビというメディアなんてとっくの昔に無くなってますぜ。自分の言っていることがおかしいと思わないとは、さすがIPv4のアドレスはアメリカが歴史的理由で占有しているから振り分ければいいなどと、気軽に言い出す人だけある。
通訳を仕事とする人の中には、同時通訳を能くする者がいる。一体どうやっているんだろうと不思議に思う。
ある時、チャットをしていて、「日本人は閉鎖的じゃないけどね。単に日本語以外を使うのが面倒なだけ。」と言いたかった事があった。いつもなら問題なく英語が出てくるはずだが、問題なのは、これを日本語で考えてしまったことだった。英語に訳そうとしても、全然訳が浮かんでこない。恐ろしく時間がかかって、"i dont think japanese tend to be closed. they just dont bother to use other languages."と言った。これを考えるためには、一度日本語の文章を忘れるだけの時間が必要だった。
しかし、日本語で考えると不思議な文章だ。日本人としては、「(俺が思うに)日本人は閉鎖的じゃないけどね。(日本人は)単に日本語以外(の言語)を使うのが面倒なだけ。」であることは自明であって、括弧内の事は、わざわざ言うまでも無いが、なぜ英語では省略できないのだろう。
というわけで、英語を話すには英語で考えなければならず、日本語もまた然りなのだが、いったい同時通訳者という人間はどうやっているのだろう。
しかし、英語で会話ができるようになりたい。チャットはできるのに会話ができないとはどういう事だろう。まあ、全然使っていないからに他ならないが。
というわけで、グリースを買いに寺町に行った。PCショップに入る前に、古本屋に立ち寄ったところ、面白そうな本が何冊も投売りされていたので、購入した。CPUとクーラーの間に塗るグリースといえば、シリコングリースだとばかり思っていたのだが、どうもセラミックグリースというものがたくさんあった。むしろシリコングリースはひとつしかなかった。そんなわけで、セラミックグリースを購入してみた。
さて、さっそく塗るわけだが、今のケースは少し面倒くさい。まずCPUファンに向けて、直接に外気を取り入れるためのダクトを外し、ケースファンが邪魔なのでそれも外す。クーラーを外し、古い乾燥した粉っぽいグリースを取り除く。買ってきたセラミックグリースの説明には、シリコングリースを取り除くにはアルコールがよいという。残念ながら無水アルコールは持っていないが、96度のウォッカがあるのでそれで代用することにした。どうせ壊れやしないだろう。そして新しいグリースを塗り、すべてを元に戻す。
今現在は、非常に快適に動作している。ファンがとても静かになった。過負荷をかけてみても、ファンの回転数が最高まで上がらない。しかも温度も低い。しかし、性能が出るまで付着後20時間かかると書いてあるのはどういうことだろう。
最近、やたらにCPUの温度が高めだと思っていたら、どうもCPUクーラーがホコリまみれなのが原因なようだ。とりあえず取り外して掃除した。そして掃除を終えて、シリコングリースを持ち合わせていないことに気がついた。みれば、CPUとクーラーの表面に、なにやら乾燥した粉っぽいものがついている。聞説、最近のCPUには、あらかじめ熱伝導性のよいシートが貼ってあるから、グリースを使う必要も無いという。今使っているPCは、面倒だったので組み立て済みのものを買ったものだから、グリースかシートかは分からないが、とにかく、明日はグリースを買ってこなければ。
以下のコードはまったく問題の無いC++0xのコードである。
template < typename T >
void f( T x )
{
x.value ;
}
struct Bar
{
int value ;
} ;
int main()
{
f(Bar()) ;
}
見ての通り、このコードは、template parameter T がメンバ変数 value を持っていることを要求している。このコードはジェネリックなコードのはずだ。
さて、できることならば、この関数テンプレートにコンセプトを導入したい。しかし、それは不可能だ。なぜならば、コンセプトでメンバ変数を要求することはできない。では一体どうすればいいのか。こうすればいいのではないかと思うのだが、なぜかコンパイルが通らない。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
typename value_type ;
requires std::CopyAssignable< value_type, int > ;
value_type & value(T & x) { return x.value ; }
}
template < typename T > requires Foo< T >
void f( T x )
{
value(x) = 0 ;
}
associated typeは実に不思議だ。大抵は関数の戻り値の型を表すのに使われる。(14.9.1.2.2)。どうも、ネストされた型として使えるようだ。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
typename type ;
type T::func() ;
}
template < typename T > requires Foo< T >
void f( T x )
{
x.func() ;
// T::typeが使える。
std::cout << typeid(typename T::type).name() << std::endl ;
}
struct Bar
{
Bar func(){ return Bar() ; }
} ;
int main()
{
f(Bar()) ;
}
しかし、以下のコードはコンパイルできない。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
typename type ;
}
template < typename T > requires Foo< T >
void f( T x )
{
std::cout << typeid(typename T::type).name() << std::endl ;
}
struct Bar
{
typedef Bar type ;
} ;
int main()
{
f(Bar()) ;
}
もし、associated typeで、テンプレートパラメーターのネストされた同名の型を使いたい場合は、default valueを使うことで実現できる。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
typename type = T::type ;
}
なお、concept_mapで別名を割り当てることも可能だ。例えば、Bar::typeではなく、Bar::typoだったとしても、concept_mapを使えば、ご覧の通り。
struct Bar
{
typedef Bar typo ;// おおっと!
} ;
concept_map Foo< Bar >
{
typedef Bar::typo type ;// おkおk
}
そういえば、今までクラス内部でネストされたtypedefをなんと言うのか分からず、いろんな表現を使っていたが、規格用語としては、Nested type names(9.9)というらしい。そのまんまだ。
例えば次のコードで、
template < typename T >
void g( T x ) { }
template < typename T > requires Foo< T >
void f( T x )
{
g( x ) ;
}
コンセプトFooを記述したいとする。次のように書くのは誤りである。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
}
なぜならば、この型を引数として、関数gを呼んでいるからだ。当然、関数gもコンセプトで要求しなければならない。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
void g( T ) ;
}
さて、この時点でもう厳格すぎて嫌になる人間と、そうでない変態がいる。そういう変態は、今は主にBoost界隈に生息しているが、いずれC++0xに移住してくるだろう。そのような変態のためには、続きがある。もし、関数fが次のようであったならば、どのようにコンセプトFooを記述すればよいのだろうか。
template < typename T > requires Foo< T >
void f( T x )
{
std::cout << x << std::endl ;
}
このコンセプトを苦もなく書けるならば、真の変態と自称してよい。答えは次のとおり。
auto concept Foo < typename T >
{
requires std::CopyConstructible< T > ;
std::ostream & operator << ( std::ostream &, T & ) ;
}
なお、このコードに対するConceptGCCのエラーがひどく読みづらい。まだ改善の余地ありだ。VC++のように、typedefされた型名で出力すれば、少しはマシになるのだが。
ConceptGCCでは、以下のコードがコンパイルできない。
auto concept Addable<typename T, typename U = T>
{
typename result_type;
result_type operator+(T, U);
}
何故だろう。ひょっとしたら、Associated typeはメンバ関数にしか使えないのかもと思ったが、現行のドラフト規格には、
Associated functions describe functions, member functions, or operators (including templates thereof)(14.9.1.1.1)
An associated type specifies a type in a concept body. Associated types are typically used to express the parameter and return types of associated functions.(14.9.1.2.2)
とあるので、Associated functionに使えることになる。そしてAssociated functionはメンバ関数限定ではない。どうなっているのだろう。
追記、後で試したらコンパイルできた。どこか文法間違いでもあったのだろうか。
コンセプトは、型にたいする要求をする機能だ。「俺はこの型にたいしてかくかくしかじかの操作をする。そのほかのとらとらうまうまな操作は絶対しない。安心しろ」と公言する機能である。しかし、とても厳格だ。
例えば、二つの引数を足して返す、簡単な関数テンプレートを実装したいとしよう。C++03を話すことのできる者ならば、簡単に書くことができる。
template < typename T > requires Addable< T >
T add( T const & x, T const & y )
{
return x + y ;
}
これ以上に無いくらい簡単だ。必要なのは、コンセプトAddableを書くだけだ。どのように書けばいいのだろう。コンセプトの初心者は、次のように書くことだろう。
auto concept Addable < typename T >
{
T operator + ( T const &, T const & ) ;
}
しかしこのコードはコンパイルエラーになる。一体何が悪いというのか。それはadd関数が値を返しているからだ。値を返すには、コピーコンストラクタがなければならない。然るによって、コピーコンストラクタを明記してやらねばならない。一体何故コピーコンストラクタのようなトリビアルなものですら要求しなければならないのかといぶかる人がいるかもしれないが、すべての操作を要求しなければならないのだから、しかたがない。
auto concept Addable < typename T >
{
T::T( T const & ) ;
T operator + ( T const &, T const & ) ;
}
しかしもし、add関数を次のように書いたならば、このコンセプトはエラーになる。
template < typename T > requires Addable< T >
T add( T const & x, T const & y )
{
T temp ;
temp = x + y ;
return temp ;
}
なぜならば、T型の変数を、初期化せずに宣言したということは、デフォルトコンストラクタがなければならないからだ。また、代入演算子も使っているので、それも要求しなければならない。代入演算子と、new, new[], delete, delete[]だけは、メンバ関数として要求する必要がある。
auto concept Addable < typename T >
{
T::T( ) ;
T::T( T const & ) ;
T T::operator = ( T const & ) ;
T operator + ( T const &, T const & ) ;
}
これはとても面倒くさい。そこで、標準ライブラリとして、あらかじめよく使いそうなコンセプトを定義してある。
namespace std {
auto concept CopyConstructible< typename T >
{
T::T( const T & ) ;
}
auto concept DefaultConstructible< typename T >
{
T::T( ) ;
}
auto concept CopyAssignable< typename T, typename U = T >
{
typename result_type;
result_type T::operator = ( U const& ) ;
}
}// namespace std
これを使えば、上記のコードは次のように書ける。
auto concept Addable < typename T >
{
requires std::DefaultConstructible< T > ;
requires std::CopyConstructible< T > ;
requires std::CopyAssignable< T > ;
T operator + ( T const &, T const & ) ;
}
std::Assignableは、ConceptGCC 4.3.0 Alpha 7のconceptsでは定義されていなかった。
なお、実はstd::Addableというのもある。ただしこれはoperator +とresult_typeだけのコンセプトなので、注意されたし。
ともかく、コンセプトを使うには、クラスやテンプレートに関してとてもとても詳しくなければならない。
ConceptGCCの説明には、"Associated templates and function templates are not implemented." と書いてある。Associated templatesとは(14.9.1.2)に書いてある。associated class templatesのことであろうか。つまり、(14.9.1.2.3)に、"An associated class template specifies a class template in a concept." と書いてるように、以下のようになる。
auto concept UseAssociatedClassTemplates< typename T >
{
template < typename U > class X ;
}
このコードは確かにコンパイルが通らなかった。では、function templatesとは何だろう。これは関数テンプレート全般を意味するが、おそらくは(14.9.1.1.7)の、"Associated functions may specify requirements for function templates and member function templates."のことであろう。
auto concept UseFunctionTemplateAndMemberFunctionTemplate < typename T >
{
// for function templates
template < typename U >
void f(T, U) ;
// and member function templates
template < typename Iterator >
T::T(Iterator first, Iterator last) ;
}
このコードをコンパイルすると、どうもfunction templatesに関するパースは実装されているようで、"note: this feature is currently unsupported, but will be in a future version" と出力される。ただし、member function templatesに関しては、まだパースもできないようだ。
さて、コンセプトでは、代入演算子はメンバ関数として宣言しなければならない。しかし、メンバ関数はconcept_mapで定義できない。以下のような例はどうすればいいのか。
auto concept MyConcept< typename T >
{
requires std::CopyConstructible< T > ;
T T::operator +=(T const &) ;
}
template < typename T > requires MyConcept< T >
void func( T x )
{
x += 1 ;
}
struct Foo
{
int value ;
} ;
concept_map MyConcept< Foo >
{
//エラー、メンバ関数は定義できない。
Foo Foo::operator +=(Foo const &)
{
this->value += value ;
return *this ;
}
}
int main()
{
Foo foo ;
foo.value = 10 ;
func(foo) ;
}
このケースに、Herb Sutterとかの偉い人の論理を適用すると、こうなるのだろう。
auto concept MyConcept< typename T >
{
requires std::CopyConstructible< T > ;
T assign_add(T & l, int r){ l += r ; }
}
template < typename T > requires MyConcept< T >
void func( T x )
{
assign_add(x, 1) ;
}
struct Foo
{
int value ;
} ;
concept_map MyConcept< Foo >
{
Foo assign_add(Foo & l, int r)
{
l.value += r ;
return l ;
}
}
int main()
{
Foo foo ;
foo.value = 10 ;
func(foo) ;
}
このようにすれば、すでにoperator +=が定義されている型はそのまま使えるし、concept_mapによる、すり合わせというか都合あわせというか、定義もできる。しかし問題は、begin()のような例ならいいとしても、代入演算子にたいしてまで、こうする必要があるのだろうか
コンセプトというのは、メンバー関数の演算子でも、非メンバー関数として指定する。
14.9.1.1.4
With the exception of the assignment operator (??) and operators new, new[], delete, and delete[], associated functions shall specify requirements for operators as non-member functions.
だからもし、T operator + (T,T)を非constとして使いたい場合(まあ、確かにそんなケースはあまり期待されて無いけどさ)、T operator + (T &, T)としなければならない。理屈としては分かるが、非常に非人間的だ。
しかし、代入演算子はメンバ関数として記述しなければならないという例外がある。これには当然 operator = のみならず、operator +=や-=なども含まれる。非常に分かりにくい。
しかし、メンバ関数はconcept_mapでadaptできないというのは、代入演算子にも適用されるらしい。どないせーちゅーねん。
Unixは嫌いだが、仕方なくConceptGCCを導入してみた。Windowsで使う場合は、Cygwinをインストールして、gcc, libmpfr1 and libmpfr-develパッケージも入れておくこと。そしてルートディレクトリで、
tar jxf conceptgcc-version-platform.tar.bz2
を実行する。普通に解凍してCygwinのディレクトリにぶち込むだけではなぜか0バイトのファイルばかりに。何故だろう。
まあそれはさておき、ConceptGCCを使うには、conceptg++を使う。さっそく色々と試してみたのだが、おそらくコンセプトは、長く初心者お断りの機能になるだろう。かなり分かりにくい。これまともに使うのは無理があるだろう。
C++0x の Concept の詳しい説明を書こうとしたが、一体、何から始めるべきなのか。
ConceptGCCのFAQに、concept_mapはメンバ関数はadaptできないと書いてあるけれど、できるんじゃないかなぁ。
if f is a non-static associated member function and the concept map contains one or more member function or member function template definitions in the type X and with the same name as f, E is x.f(parm10, parm20, ..., parmN0), where name lookup of x.f refers to the definitions of X::f in the concept map
コンテナにとてつもなく重いオブジェクトを入れたいとする。そのクラスは、move可能ではなく、コピーもできないか、とてつもなく重いときている。これを従来のコンテナに入れようとすると、問題がある。push_backなどのオブジェクトを挿入するメンバ関数は、オブジェクトを必要とするからだ。だから、まずオブジェクトを構築してpush_backに渡し、push_backはそのオブジェクトをコピーしてメモリ上にオブジェクトを構築する。先ほども言ったように、このオブジェクトのコピーは、構築と変わらないほどコストがかかる。なぜ二回も構築しなければならないのか。一回構築すれば、それで十分なはずだが、現在のコンテナはそれを可能にするインターフェースが無い。
そのクラスの中には、一部は動的に確保するmove可能なメンバもあるかもしれない。しかしmove可能ではないメンバ変数もたくさんある。一体どうすれば良いのか。例えば次のように実装してみよう。
//初期化用のためだけにある糞クラス
class Foo_data
{
public :
Foo_data(int a, int b, int c) {/*時間のかかる処理*/}
} ;
class Foo
{
public :
Foo( ){/*何もしない*/}// トリビアルなコンストラクタ
//実行のとても時間のかかるコンストラクタ
Foo( int a, int b, int c ){/*時間のかかる構築処理*/}
Foo( Foo const & foo){/*時間のかかるコピー処理*/}
// あらかじめ生成しておいたFoo_dataからは、高速にコピーできる
// 糞コード。
Foo & operator = ( Foo_data const & data){/*それほど時間がかからず構築処理ができる*/}
// move可能でないメンバ、Foo_dataからは高速に初期化できる。
// move可能なメンバ
} ;
// これでは時間のかかるコンストラクタを二度呼び出すことになってしまう。
c.push_back(Foo(1, 2, 3)) ;
// これはそこそこ早い。
Foo_data data(1, 2, 3) ;// あらかじめ構築しておく
c.push_back(Foo()) ;// 初期化されていない空のオブジェクトを挿入
c.back() = Foo_data ;// やっとここでまともに初期化
オーケー、このコードは言うまでもなく糞だ。説明が必要なほど糞だ。問題点とは、Fooクラスの構築はとても重い処理であるということだ。だから、あらかじめ構築に必要な重い処理だけ、Foo_dataでさせておく。コンテナには、初期化されていない空のオブジェクトを挿入したあと、Foo_dataを代入して、初期化を終える。
このコードの意図は説明されなければ分からない。コードは糞だし、これを使うユーザは大変だ。しかし、この手の問題は、コンパイラの最適化ではどうにもならないところがある。では一体どうすれば良いのか。そもそも、コンテナは、内部でPlacement newを使い、オブジェクトを構築しているわけだ。そのplacement newの時にコンストラクタを呼び出すわけだが、そのコンストラクタに任意の引数を渡せれば、この問題は解決する。そのために、Variadic templatesを使えばよい。
class vector
{
public :
template < typename... Args >
void push_back( Args... args ) ;
} ;
このpush_backは、任意の数の引数を受け取る。その引数を、そのまま要素のコンストラクタに渡す。つまり、push_back(1, 2, 3)と、push_back(Foo(1, 2, 3))は同じ動作をするわけだ。ただしコンストラクタは一回しか呼び出されないので、高速である。
しかし問題が無いわけではない。そもそも初心者や、C++03プログラマが、push_back(1, 2, 3) というコードを見て何を思うか。コンストラクタ引数を渡しているとは思わないのではないか。むしろ、
c.push_back(1) ;
v.push_back(2) ;
v.push_back(3) ;
と、三つの要素を挿入できる便利な機能が付け加えれたのだと、考え違いをするのではないか。紛らわしいことに、C++0xでは、initializer listを使って、一度に三つの要素を挿入することもできるのだ。
v.push_back({1, 2, 3}) ;三つの要素を挿入する
また、この実装では、explicitなコンストラクタでも、呼び出せてしまう。なぜなら、実際にコンストラクタを明示的に呼んでいるからだ。
問題はまだある。C++ではゼロというのは特別なリテラルだ。0はどんなポインタにも代入できてしまう。しかし、C++03で合法なコードが、この提案が通った暁には、C++0xでは以下の通りになってしまう。
vector<int*> v;
v.push_back(); // オーケー、引数の無いデフォルトコンストラクタを呼び出す。
v.push_back(nullptr); // オーケー、NULLを表す新しい予約語、nullptrが導入されることが決まっている
v.push_back(0); // エラー、int *はintで初期化できない
なぜこのようなことが起こるのか。それは、最後のpush_back呼び出しは、int *に対して、int型でコンストラクタを呼び出そうとするからだ。ポインタはゼロの代入に関して特別なルールがあるが、int型全般に対して、そのような特別なルールは無い。この問題に関しては、conceptを使えば解決できる。ペーパーでは、mapコンテナのために、pairも同時にVariadic Templatesを受け取るので、言及されている。これもオーバーロードを使えば解決可能である。
もうひとつ、以前の提案では、emplaceには二つのオーバーロードがある。オブジェクトそのものをとるemplaceと、オブジェクトのコンストラクタの引数をとるためのヒントを加えたバージョンだ。しかしこれがうまく機能しないコードが発見された。ここでは詳しく説明しないのでN2680を読んで欲しいが、まあnastyな問題だ。
では解決方法は何か。まず、オーバーロードを使用しないことだ。別名をつけてやればいい。
template<typename... Args> void emplace_front(Args&&... args);
template<typename... Args> void emplace_back(Args&&... args);
template<typename... Args> iterator emplace(const_iterator position, Args&&... args);
つまり、既存のpush_backなどのメンバには手を加えないのだ。push_backは以前と変わらず、実際の値しか受け付けない。コンストラクタ引数を渡したい場合は、明示的にemplaceを使う必要がある。setの場合は、emplace_hintなどとするしかないが、まあ仕方が無いだろう。
私個人としては、この案を支持する。それに、コードが分かりやすくなる。push_backなどは既に意味が周知となっているので、この意味に別の意味を付け加えるのはよくないことだ。
もうひとつ、ファンシーなアプローチがある。ファンシーすぎて解説する意欲が沸かない。ひとつ言えることは、どう考えても糞だということだ。だから、気になるからといって、わざわざ自分で読む必要はない。もっとも、あなたがBoostの実装を気にするほどの言語マニアであれば読んでもよい。一体どこがシンプルでイージーなのかと小一時間。私の考えでは、このファンシーなアプローチを実装することは可能であると思う。Variadic templatesとTupleとメタプログラミングと、いかなる変態的コードであろうとも恐れぬ蛮勇があれば、実装可能だろう。彼のコードにはコメントが書かれているであろう。You are not expected to understand this. と。
Engineering Windows 7
マイクロソフトのEngineering Windows 7 ブログ
Jon DeVaan と Steven Sinofsky によるWindows 7のブログらしい。日本語版もあり、どうやら人間が訳しているらしい。少なくとも、読めない訳ではない。こちらの方はコメントが一切無いが。
規格にはN1704で示されているようなN番目の型を得る方法が入っていない。すると、こうするのだろうか。
template < size_t N, typename Head, typename... Tail >
struct nth_type : public nth_type<N-1, Tail...>
{ } ;
template < typename Head, typename... Tail >
struct nth_type< 1, Head, Tail... >
{
typedef Head type ;
} ;
何か書かなければいけないと思いつつ、何も書いていない。やるべきことが多すぎる。
とにかく、SC22/C++WG会議を見学することと相成り、必要なペーパーを読んでいる。今は議論の結果変わった規格などもあり、最新についていくのはなかなか難しい。
atomic周りの知識が怪しかったので、もう一度規格を読み直した。N2427が非常にためになる。おかげでだいぶ理解を深めることができた。
今はN2679のInitializer Lists for Standard Containersを理解するために、initializer_listについて学ぶべく、N2215を読んでいる。かなり長いが、これに関しては問題ないだろう。
問題は、N2680のProposed Wording for Placement Insertだ。この問題に関しては、前者の提案、N2345もあらかじめ読んでおく必要があり、なかなか難しい。
前回、VCのハッシュマップがあまりにも遅いことを疑問に思っていた。テストした結果、要素の検索が定数時間ではなく、対数時間だったのだ。さて、VC9のSP1も出たことだし、パフォーマンスの向上も謳っているので、さっそくunordered_setを試してみた。
前回のコードをそのまま実行すると、なぜかDinkmwareのunordered_setが動かない。いや、要素の挿入と検索はできるのだが、それから先が動かない。五分ほど考えて、check関数から戻っていないのだと結論した。しかしcheck関数が終わらない理由はひとつしかない。Dinkmwareのunordered_setのデストラクタが終わらないからだ。
ためしにデストラクタの実行にかかる時間を調べてみると、恐ろしく長い時間がかかっている。信じてくれ、百万件の要素を入れたunordered_setのデストラクタの実行を待つ間に昼寝ができる。もちろん、setやBoostのunordered_setでは、デストラクタの実行は一秒以内に終わる。いい加減、VCはDinkmwareと手を切ったほうが良いんじゃねえの。この前だってauto_ptrの本当にしょうもないバグがあったし。
しかし不思議なことに、clear()の実行にかかる時間を調べてみると、とても早い。しかも、clear()を呼び出した場合は、デストラクタの実行も早い。一体どうなっているんだ。clearはひょっとしたらメモリをリークしているのではないかと疑ったが、どうもリークしている気配はない。はて、これが例のVC++ブログに書いてあった、eraseのパフォーマンスに問題がある、と言うやつだろうか。しかしclearに問題は無いとはこれ如何に。
#include <iostream>
#include <set>
#include <unordered_set>
#include <boost/unordered_set.hpp>
template < typename SET >
void check(LARGE_INTEGER & end_user_code)
{
std::cout << typeid(SET).name() << std::endl ;
LARGE_INTEGER now, dt, freq ;
QueryPerformanceFrequency( &freq ) ;
SET set ;
QueryPerformanceCounter( &now ) ;
for ( unsigned int i = 0 ; i != 1000000 ; ++i )
{
set.insert(i) ;
}
QueryPerformanceCounter( &dt ) ;
std::cout << "insert : " << double(dt.QuadPart - now.QuadPart) / double(freq.QuadPart) << std::endl ;
QueryPerformanceCounter( &now ) ;
for ( unsigned int i = 0 ; i != 1000000 ; ++i )
{
set.find( i ) ;
}
QueryPerformanceCounter( &dt ) ;
std::cout << "find : " << double(dt.QuadPart - now.QuadPart) / double(freq.QuadPart) << std::endl ;
QueryPerformanceCounter( &end_user_code ) ;
// clear()をコメントアウトすると、
// Dinkumwareのunordered_setのデストラクタが非常に遅くなる。
set.clear() ;
}
int main()
{
LARGE_INTEGER now, dt, freq ;
QueryPerformanceFrequency( &freq ) ;
for ( int i = 0 ; i != 5 ; ++i )
{
check< std::set< unsigned int > >( now ) ;
QueryPerformanceCounter( &dt ) ;
std::cout << "destructer : " << double(dt.QuadPart - now.QuadPart) / double(freq.QuadPart) << std::endl ;
check< boost::unordered_set< unsigned int > >( now ) ;
QueryPerformanceCounter( &dt ) ;
std::cout << "destructer : " << double(dt.QuadPart - now.QuadPart) / double(freq.QuadPart) << std::endl ;
check< std::tr1::unordered_set< unsigned int > >( now ) ;
QueryPerformanceCounter( &dt ) ;
std::cout << "destructer : " << double(dt.QuadPart - now.QuadPart) / double(freq.QuadPart) << std::endl ;
}
}
追記:
リリースビルドをすっかり忘れていた。リリースビルドならば、デストラクタの不可解で非現実的な遅さは解消された。リリースビルドの場合、要素の挿入はBoostより三割ほど遅いだけだが、しかし検索はstd::setの二分の一の時間かかる。Boostより十倍遅い。デストラクタもやはりBoostとは十倍遅いので、一桁が違うが。しかし、このままでは、デバッグビルドで実行ができない。
検索にかかる時間が気になる。std::setより二倍早いだけというのがどうも解せない。Boostの実装はstd::setより二十倍も早いだけに、なおさら不満だ。定数時間?
さらに、メモリ使用量も、Boostより多いようだ。うーん。不満たらたら。
追記2:
Checked Iteratorを無効にすると、Dinkumwareの実装は、Boostの実装に比べ、10倍の遅さから、5倍の遅さになった。
Feature PackにもあったTR1、MFCの追加とバグ修正、コンパイラのバグ修正など。unordered_setが早くなっているらしい。ただし、eraseだけはまだ遅いらしい。確かめてみなくては。
古本屋で滝口入道と言うタイトルの本が売っていた。はて、滝口入道といえば、維盛の善智識だったはず。表紙には、右から左に、「道入口瀧」とかいてあった。昭和十三年出版とあった。せっかくなので買うことにした。
滝口入道とは、高山樗牛という人が書いた唯一の小説らしい小説だ。ただ、現代人がこれを読んで、小説だと見るのは難しい。というのも、本人が書いた文章というものが存在しないからだ。ほとんどが古典や漢詩や仏教書からのコピペである。現代においてこの手の小説を発表したならば、袋叩きにあうこと受けあいだが、しかし時代が時代と言うか、こんな切り貼り小説が、明示を代表する時代小説だというのだから、昔はよほど小説に恵まれていなかったらしい。
まあ、ひとつ弁護しておくと、独自の文体などに価値を見出される時代ではなかった、と言うべきかも知れない。
TG Daily - Reporters booted from Black Hat for hacking
なんとも皮肉な話だ。そもそもBlack Hatとは、日夜セキュリティ上の脆弱性を探している連中が集まる会議である。これは起こるべくして起きたとでも言おうか。
完璧という言葉は、現代の日本においても、広く使われている。この言葉は非常に深く日本語になじんでいるため、我々はつい、これが漢語であることを忘れがちである。しかし、この言葉は、実は二千年以上前の、ある中国の故事からきた言葉であると言ったならば、果たして信じられるであろうか。古くは印刷技術、最近ではコンピュータの発達により、我々は漢字を急速に忘れつつある。この完璧の璧(へき)という漢字も、よく壁(かべ)と間違えられてしまう。よく部首を見ると、璧は玉あしだが、壁は土あしである。これらは違う漢字で、その指すところのものも異なる。ではへきとは何か。Googleに聞いてみると、なにやら中央に穴の開いている円盤であることが分かる。これは中国の伝統的な飾りなのだ。
刎頚の交はりというのは、今の日本において、それほど頻出する言葉ではない。友のためなら首を刎ねられてもかまわないという、なんとも物騒な言葉だ。一般常識として知っている日本人は多いが、日常会話で使うということはまずない。昔、ロッキード事件に関して質問された小佐野賢治が、自分と田中角栄との仲は刎頚の交わりであると発言したことがあったが、あれは誤りである。というのも、この言葉は本来、私利私欲を超えた国のための忠義心から生まれた言葉であって、袖の下を通す俗悪な役人が発していい言葉ではない。
紀元前284年のことである。趙の恵文王は、ある手紙を前にして、頭を抱えていた。手紙は秦の昭襄王からのもので、趙王の持つ和氏の璧を我が秦国の十五城と交換して欲しい、というものであった。和氏の璧とは、その四百年ほど昔からある、有名な玉璧であった。どのような巡り会わせか、今は恵文王の手にある。この手の宝を所有しているということは、王の力の強大なことを内外に示す手段でもあったのだ。その璧を譲ってくれというのである。しかもタダではなく、十五城もやるというのだ。確かに天下に名高い名玉の璧ではあるが、十五城と交換というのであれば、まんざら悪い話でもない。恵文王は武功高い良将と廉頗に相談した。そもそもの問題は、大国の秦が小国の趙と、誠実に取引をするかどうかということである。璧だけ取り上げて、対価の城については、知らぬ存ぜぬで通す可能性は十分にある。しかし、もし璧を渡さなければ、秦に趙を攻める良い口実を与えてしまうことにもなりかねない。
恵文王をはじめとして重臣たちも、どうしたらよいものか判断が下せなかった。そもそも、一体誰を使者として秦に遣わせばいいのか、それすら決まらないでいる。そこで繆賢という者が、自分の食客に藺相如という賢者がいると申し出た。恵文王が会って、璧を渡すべきかどうか訪ねた。
「秦は強国で、趙は弱国です。渡さないわけには参りますまい」
「しかし、秦が璧だけ取り、趙に城を渡さなかった場合はどうするのだ」
「秦が城をもって璧を所望しているのに渡さなければ、非は趙にあります。趙が璧を渡したのに、秦が城をよこさないというのであれば、非は秦にあります。この二つを比べれば、渡したほうが得策でしょう。もし秦が城を渡さなければ、天下に秦の無道を知らせることができますから」
「では誰を使者に立てるべきだろうか」
「他に人がいないというのであれば、私が行きましょう。もし秦が城を与えるならば、璧を渡してきましょう。もし城を渡さなければ、璧を完うして趙に帰ってきます」
璧を完うす、というのは、璧を無事に趙に持ち帰るという意味である。これが完璧の由来である。ご存知のとおり、中国語はSVO文型なので、「完㆑璧」、となる。恵文王は相如に璧を奉じ、西のかた秦に入らせた。
さて、相如が秦に着いてみると、昭襄王はさっそく謁見した。相如から璧を受け取ると、左右の臣下や美人に見せびらかした。左右の者は万歳を叫んでいる。相如は秦王に十五城を渡す意が無いことを見て取ると、御前に進んで言った。
「実はその璧には瑕があります。教えておきましょう」と。
そして璧を受け取ると、すぐに下がって柱に向かった。怒りのあまり逆立った髪が冠をつらぬかんばかりであった。
「大王は十五城と引き換えに璧を所望した。趙王および群臣は皆、秦が約束を守らずに璧だけを受け取るだろうと言い張り、璧を渡そうとしない。しかしこの私は、人は身分を越えた交友すら破らないというのに、まさか秦ともあろう大国が約束を破るはずがない、と説き、またたかが璧ひとつをもって秦と事を構えるべきではない、とも説き聞かせ、このたび趙が璧を奉じるに至ったのだ。璧を奉じるに際し、趙王は身を清め食を断ち斎戒すること五日。今、大王使者に対して礼節なく、璧を得るや、女などに見せびらかす。十五城を与えるという約束はどうなったのだ。あえてこの璧を奪う気ならば、我が頭と共に砕いてしまおうか」
相如は柱をにらみ、璧を持った手を振り上げた。
秦王は璧が壊れることを恐れて、有司を呼んで地図を指し示し、ここから十五都を趙に与えると言った。相如は信じず、秦王にむかって言った。
「和氏の璧は天下の宝、趙王は璧を送るとき、斎戒すること五日なり。今大王も亦た宜しく斎戒五日して、国賓を迎える礼を取ってもらいたい。璧はその後にお渡ししよう」
秦王はこれを聞き、強いて璧を奪わず、五日間の斎戒に入った。しかし相如は、斎戒したからといって約束を守るとも思えず、従者に下人の格好をさせて、璧を持たせ、ひそかに趙へ帰した。さて、五日の斎戒を終えた昭襄王は、国賓の礼をとって藺相如を迎えた。相如は言った。
「秦は繆公以来二十余君、いまだかつて約束を守った者がおりません。そのため、人をして璧を持ち帰えらせました。秦は強く趙は弱し、趙が秦と事を構えるたがることはありません。大王がまず十五都を割いて趙に与えれば、趙は必ず璧を奉じるでしょう。ただし、私の大王を欺いた罪はどうしようもありません。この上は釜茹でにでもされたがよいでしょう。」と。
武将達が即座に相如を囲んだ。しかし秦王はこれをとどめて言った。「今相如を殺しても、璧は得られんし、趙を敵に回すだけだ。相如は厚く遇して帰してやれば、趙王もたかが璧ひとつで秦を欺くことはないだろう」と。こうして相如は無事に趙に帰った。感心した趙王は相如を上大夫に任じた。
その後、秦は城を趙に与えず、趙も璧を秦に与えることはなかったという。また秦は趙を攻め、石城を落とし、二万人を殺した。
秦は兵を澠池というところまで進めて、趙に使者を送り、澠池で会を開こうと告げた。趙王はまたも頭を抱えることとなった。秦のことだから、うかつに出て行けば、会の途中で殺されるか、人質となって国を奪われるに決まっている。しかし、廉頗、藺相如が、王が行かなければ、趙の弱くしてかつ怯なるを示すだけだと進言した。趙王は相如を従えて澠池に向かった。廉頗は境まで送り、こう言った。「澠池まで行って、会遇の礼を終え、帰るまでに、三十日を過ぎることはないでしょう。三十日経っても帰らなければ、太子を立てて王とすればいいでしょう。そうすれば秦に付け入る口実は与えません」と。王は廉頗に同意した。そして、ついに澠池で秦王と会した。
酒宴も進んだとき、秦の昭襄王が言った。「趙王は音を好むと聞く。ひとつ瑟を奏してもらいたい。」
瑟とは琴の一種なのだが、遊女の弾くものである。賎しくも王の弾くものではない。趙の恵文王は、頼まれた以上嫌とも言えず、恥を忍んで瑟を弾いた。すると、秦の記録係である御史が進み出でて、「某年月日、秦王、趙王と会飲し、趙王をして瑟を鼓せしむ」と記録した。これではまるで、趙王が秦王のために賎しい瑟を弾いている、すなわち趙は秦の属国であるということになってしまう。ここで相如はすかさず、酒を入れる土器、盆缻を手に、秦王の前に進んで言った。「秦王は秦の伝統音楽に巧みであると聞いております。ひとつこの盆缻を打って酒興をそえてくださいますよう」
秦というのは、中国でも辺境に位置して、まだ中国式の礼儀に浴することが浅く、茶碗を打ち鳴らすような粗野な文化が色濃く残っていたのである。もちろん、これも王たる者の行うことではない。秦王は当然怒るばかりである。相如はさらに秦王の懐近く進み、「私と大王の間はわずかに五歩、私の頚血を大王にあびせることもできますぞ」と言った。無論、自分の首の血がかかるころには、王も命はないのだという意味である。駆けつけようとする兵士をも一喝して退けた。秦王はしかたなく、盆缻を手に取ると、一たび打った。相如は趙の御史を呼んで、「某年月日、秦王、趙王の為に缻を撃つ」と記録させた。
またその後も、秦の群臣の、「秦王の長寿を祈願して、趙の十五城を献上なされてはいかが」という物言いに対し、「秦こそ趙王の長寿を祈願して、首都咸陽を献上すべきだ」と言い返した。こうして酒宴が終わるまで、ついに秦が趙の揚げ足を取ることはできなかった。
こうして趙の恵文王は、無事に国に帰った。そして相如の功をたたえて、上卿に任じた。上卿とは、将軍より上の地位である。これを聞いて面白くないのは、ほかならぬ将軍の廉頗だった。「自分は命をかけて斉と戦い、その功あって将軍となったのに、藺相如は口舌をもって、我が上にいるとは。しかも奴は、もともと何の位も持たない賎しい食客だったではないか。もし今度相如を見かけたならば、必ず恥をかかせてくれよう」と怒鳴ったという。相如はこれを聞くと、廉頗と会うことを努めて避けた。病と称して朝廷に出ず、もし道で廉頗の車がやってきたならば、自分の車をわき道にそれさせてまで出会うことを避けた。これに嫌気がさした相如の部下達が、相そろって暇乞にきた。「私どもがいつ死ぬとも分からぬ親元をも離れ、君に仕えているのは、ただ君の高義を慕えばこそのこと。廉頗のような格下の将軍に逃げ隠れしているとは、凡人すらこれを恥じるのに、いわんや将相においてをや。私どもは情けない。どうか暇を下されますよう」と。藺相如は固くこれをとどめて言った。
「お前達は廉将軍より、秦王を恐れるに決まっているだろう」
「その通りです」
「その秦王ですら恐れずに叱咤し、その群臣を辱めたこの私だ。身不肖ながら、何でひとり廉将軍を畏れんや。ただし、なぜ強国の秦が弱国の趙を直接攻めてこないかといえば、この私と廉頗がいるからだ。もし私が廉頗と戦えば、どちらかは死なねばならぬ。私が廉頗から逃げ回っているのは、国家の急を先にして、私情を後にしているからだ」
やがて、この話が噂となり、廉頗の聞くところとなった。相如の国を思い私を後にする心にすっかり恥じ入った廉頗は、人を取り成して相如の門の前に来た。相如は廉頗が会いに来たというと断ろうとしたが、門前にいると聞き、会わずに帰すわけにもいくまいと、門の前に赴いた。すると何ということか。廉頗は半裸の上、茨の鞭を背負って地に伏しているではないか。
ところで、中国には中国の礼儀や常識というものがある。その中に、人というものは、服を着て、冠をかぶっていてこそ人間という常識がある。かの孔子の弟子の子路は、二人の暴漢に襲われ致命傷を負って地に倒れ伏したとき、紐が切れて落ちた冠を拾い、正しく頭に着けて素速く纓を結び、「見よ! 君子は、冠を、正しゅうして、死ぬものだぞ!」と絶叫して死んだという。我が朝の人は、何かにつけて裸になりたがるが、中国では服を着ず、冠をかぶらない者は、人間とはみなされないのである。
裸となり、もはや人間の尊厳を失った廉頗は、門前で地に伏したまま言う。「私は賎しい生まれで、君の国を思い寛大なることが、ここに至れるを知らざるなり。どうかこの鞭で私を気の済むまで打ってもらいたい」と。相如は思わず駆け寄り、一鞭も与えないばかりか、服を着せて許したという。以後、二人は刎頚の交はりを為したという。
参考:
史記、廉頗藺相如列伝
十八史略、趙
河出書房、新十八史略
今回は疲れた。かなり多くの前提知識が必要だ。缻を打つというのも、半裸で人前に出るというのも、中国の礼儀では人とみなされないことなのだ。人ではなく、もはや種族が違うとでも言ったほうがいいかもしれない。人間とチンパンジーほどに違うとでも言おうか。この認識がなければ、この話の真の意味は分からない。
Wikipediaにはまともな常識を持ち合わせていない人非人しかいないことは有名だが[要出典]、これには盛大に吹いた。
例えば、グーグル八分 - Wikipediaを見てみるといい。ほんの一文に、みっつも要出典タグがついている。
インターネットの利用に際しては検索エンジンを利用することが多く[要出典]、また、検索エンジンサービスは事実上寡占状態であるため[要出典]、一企業の内部的な決定で検索結果が恣意的に変更されることについては異論も多い。[要出典]
アホか? アホなのか? 現状のインターネットで、検索エンジンを利用しないというのはありえない[要出典]。検索エンジンを利用するのが当然であるからこそ、日本の看板広告の主流は、URLを載せず、「本の虫」で検索! などとしているのだ[要出典]。また、IE、Firefox、Opera、Safariなどの主要ブラウザは、検索エンジンをすばやく利用できる入力フォームを、大抵はブラウザの右上に設けているではないか[要出典]。これほど一般的な事柄に対して、一体どういう出典が必要なのだろう。日本では日本語が話されているという文章に出典が必要だろうか。この要出典タグをつけた奴は、インターネットの利用に際して、HTTPプロトコルを用いていないのだろう独自研究。
検索エンジンは事実上どころか、事実寡占状態である。Googleが圧倒的首位を占め、次にYahoo、すずめの涙ほど、gooやliveが使われている[要出典]。これを寡占と言わずしてなんと言うのか。この要出典のタグをつけた奴は、寡占という言葉の意味を知らないのだろう。
三番目はさらにアホ臭い。現状のような検索エンジンだのみのインターネット利用で、寡占状態にある検索エンジンの結果が恣意的であるのには異論がないとでもいうのだろうか。スポンサーのサイトを上位に持ってきたり、逆にスポンサーではない競合他社を表示しなかったりすることには異議が叫ばれて当然だ。しかし、ブラウザのセキュリティホールを利用して意図しない任意のコードを実行させたりするようなサイトも、警告もせずにそのまま表示していいのかという意見もあるだろう。一体ここに要出典タグをつけた阿呆は、何を考えているのだろう。もしや、「異論も多い」という文章に対して、多いとは具体的にいくつ以上を指すのかなどと言いたいのだろうか。「非難されている」だとか、「懸念されている」、「遺憾の意甚だ多し」、でも同じことだ。
これらの、あまりにも一般的過ぎる事柄にまで出典を求めると、このようなアホ臭い醜態を演じてしまうわけだ。そもそも、グーグル八分とはネット上で生まれた言葉であるのに、要出典を叫ぶ連中はネット上のソースを信用しないのだから、理解できない。
ようするにWikipediaで要出典タグを貼りまくっているのは荒らし目的の暇人か、一般常識がまったく無い狼に育てられた人間か、書かれている言語が分からない人間なのだろう。。
ある自然言語を、他の言語に正しく翻訳するのは不可能にちかい。言語は文化と切り離すことはできず、甲言語で是であることが、乙言語では非であったりする。したがって、翻訳作業は難しい。
だが、Vistaの翻訳はもう少しマシにならんものか。始めにVistaをインストールして、何はともあれまず壁紙などを変えようと思い、デスクトップを右クリックして出てきたときの、コンテキストメニューを見たときの失望といったら無い。何が個人設定だ何が。何のことだかさっぱり分からなかったが、少し考えてみると、なるほど、それはユーザごとの設定なのだから、個人設定なのだろうと分かったが、しかし、壁紙を変更するときに、わざわざユーザごとの設定を、強く意識するだろうか。別ユーザでログインすれば別の壁紙になっている。それだけのことじゃないか。他にも例を挙げれば、zoneidの確認ダイアログだ。これはNTFSの、ファイルは複数のストリームを持てる機能を使って実現しているもので、IEから落としたファイルをローカルで実行したりなどする場合に、警告が入るというものだ。それはいいとして、安全なことが分かっていて、再び確認ダイアログが表示されて欲しくない場合はどうすればいいのか。それには、「この種類のファイルであれば常に警告する」というチェックボックスをオフにする。これには私もだいぶとまどった。この文面であると、「このファイルと同じ拡張子がついているファイルに対する警告を、今後一切無効にする」と読めてしまう。しかし実はこの文章、"Always ask before opening this file"、なのだ。一体どうやったらthis fileをこの種類のファイルと訳せるんだ。どこのdumbassがこれを訳したんだ。
もうひとつ日本語の問題点には、プログラミングをしていて、Windowsに関する情報を探した場合、大抵は英語の情報を得られるが、実際に使っているWindowsでの表記が日本語では、少し困る。翻訳の候補はいくつもあり、推測するしかないという事態になる。
そこで、日本語版Windows Vistaに、英語の言語パックを入れてみることにした。
まず、言語パックを落とす。Windows Updateからでも落とすことができる。ちなみに、英語パックのサイズは、256.6MBであった。落とし終わると、インストールが始まる。1.1GBのディスク容量が必要だ。テラバイトのHDDも一般的になった今、1.1GB程度は、特に問題にはならないだろう。インストールが終わると、言語を選択できるようになる。言語を選択するには、コントロールパネルから、「表示言語の設定」を選ぶと変更できる。言語パックのインストール、変更にはシャットダウンこそ必要ないが、ログインしなおす必要がある。さて、ログインしなおしてみると、各種コントロールのサイズに違和感を覚える。これは英語フォントと日本語フォントでは、文字間や行間が異なるからだ。世の中には、ユーザの環境が自分とは違うということを無視する怠惰なプログラマがいる。そういうプログラマは、デフォルトのフォントのサイズなどが、すべて同じだと考えている。したがって、ダイアログなどの文字がずれ、ウインドウ外に追いやられて、見えなくなってしまうことがよくある。これは何も別言語のWindowsを利用しているときのみの問題ではない。ユーザは自由にデフォルトのフォントを変えられるし、第一、DPIが96とは限らない。DPIに限って言えば、VistaにはDPIのスケーリング機能があり、マニフェストなどを指定しない場合使われたはずだが。つまり明示的にフォントや行馬などを指定しないと、同じ言語のWindowsで、デフォルトのフォントを使っている場合にしか、正しいレイアウトで表示されないプログラムが書けてしまう。
さて、使ってみての感想だが、ほぼすべて英語に変わっている。explorer.exeは英語だし、notepad.exeも英語になっている。コマンドプロンプトも英語だし、dirなどのコマンドも英語を吐く。コントロールパネルの中身もすべて英語になっている。日本語IMEももちろん使えるが、IMEの辞書ツールなどの各種ダイアログは、日本語になっている。まあ、ロシア語の日本語IMEなど需要が無いだろうし、そもそも標準で英語表示にすることもできる。キーボードのレイアウトが変わるのかどうかは、元から英語配列のキーボードを使っているので分からないし、わざわざ試そうとも思わない。
先にも述べた如く、明示的にフォントを設定せず、コモンコントロールのデフォルトのフォントを使っている糞なプログラムは、正しいレイアウトにはならない。まあ、そういうプログラムは、日本語環境でも、どうせ正しくは見えない。偶然正しく見えているだけなのだ。
Vistaの英語は、日本語と比べて、文字の垂直方向の情報量が多い。これはアルファベットが、より小さいフォントでも、十分読めることが理由だ。しかし、水平方向の情報量は少ない。これは、日本語なら数文字の漢語ですむところを、英語は完全に表音文字なので、長々と書かなければならないためだ。
臥薪嘗胆といえば、中国のとある王が、恨みを忘れぬようにするため、あるは薪の上に臥し、あるは肝を嘗めた故事による、ということは、みな知っている。しかし、その詳しい話はどうだろうか。
紀元前496年の事である。越では允常が死に、子の勾践が王位についた。勾践には、范蠡という名臣がいた。ところで、呉王闔閭は、允常の死を良い機に、越を滅ぼそうと兵を起こした。しかし、范蠡は呉の動きを見抜き、先手を打って兵を進め、優れた策を用いたため、呉軍は敗走した。闔閭は流れ矢によって指に受けた傷がもとで、無念にも敗走の途中で死んだ。死ぬ間際に、闔閭はその次男、夫差に向かって言った。「夫差、而は越人の而の父を殺ししを忘れたるか」と。
父闔閭に変わって呉の王となった夫差は、固く復讐を誓った。兵を訓練し、自らは薪の上に臥した。夫差は毎夜、角ばった薪の痛みに目を覚ましては、父の遺恨を思い返すのであった。また、自分の部屋に入るものは誰であれ、父の遺命を叫ばせた。「夫差、而は越人の而の父を殺ししを忘れたるか」と。
やがて勾践は、夫差が薪の上に臥してまで復讐を狙っていることを知ると、即座に呉を攻めようとした。范蠡は勾践を諫めたが、勾践は聞き入れなかった。夫差は越軍を迎え討ち、これを破った。勾践はわずかに残った手勢と共に会稽山に逃れた。
ここにいたって、勾践は范蠡の諫言を聞かなかったことを悔いた。范蠡は勾践に説いて言った。
「常に心を持ち、苦難に耐え、質素を守れば、天地人の助けを得られましょう」
「しかし、呉の大軍に囲まれている現状は、どうすればいいだろう」
「今は、呉の臣下となり、和を請うより他に仕方がありません」
そこで、勾践は呉王のもとに種を遣わし、降伏して自らは呉王の臣下となりたい旨を述べた。夫差は越王が臣下となりさがってまで許しを請うていると聞いて満足し、降伏を許そうとした。しかし、夫差の臣、伍子胥は、越は体面を取り繕っているだけで、いずれ反抗するであろうことを見抜き、夫差を留めた。しかし、種と勾践は、呉の宰相伯嚭に賄賂を送って取り成しを求めた。伍子胥はしきりに許すべきではないことを説いたが、ついに勾践は降伏を許された。
勾践は許されて国に帰った。しかし、もはや越王ではなく、呉王の臣下なのだ。句践は常に苦い胆をそばに置き、これを嘗めては、会稽の恥を思い返すのだった。曰く、「女、会稽の恥を忘れたるか」と。また范蠡の言を守り、自ら肥桶を担って田畑を耕し、夫人には機を織らせ、粗衣と粗食に甘んじた。賢人には頭をたれて教えを乞い、貧しい者あればこれを助け、もって天地人の助けを得ることに勤めた。また、先の呉の伯嚭に賄賂を送り、呉王を欺いていた。
ある時、呉王が宮殿を建造しようとするのにかこつけて、良材二百本と美女五十人を送った。この中に、中国四大美女に数えられる、かの有名な西施がいた。西施はもともと、越の苧羅村で薪をひさぐ女であった。呉王の寵姫となってからある時、西施は癪を病んで、しばらく故郷の村に帰っていた。痛む胸元を手で押さえ、眉を顰めて歩いていたが、さすがは呉王の寵姫、どんな仕草でも美しいと、村の評判であった。ところで、その村には評判の醜女がいて、せめて西施のまねをすれば、自分も少しは美しく見えるかと、胸を押さえ、醜悪な面をしかめて村を歩いてみたが、ただでさえ醜い女の奇妙極まりない格好に、居並ぶ富人の家々は、堅く門を閉じて出でず、家に門のない貧人は妻子を連れて逃げ出したそうである。この事から、みだりに人の真似をすることを「顰に倣う」と言うようになったそうであるが、このような言葉ができるほど、西施はたぐいまれな美女であった。
夫差の西施を愛することは一通りではなかった。西施のために百花州、香水渓、西施洞、玩花池、採香径、碧泉井、館娃宮を築き、春夏秋冬、西施と共に遊んだ。かつては薪の上に臥した夫差も、今は錦褥に臥して西施と色に溺れていた。一方越では、勾践が肝を嘗めていたのである。
さて、伯嚭は事あるごとに、呉王に斉を撃つべきだと進言し、呉はたびたび斉を攻めた。越が賄賂を贈って操っているのである。その意は、呉を斉にあたらせることによって疲弊させようと言う腹である。伍子胥は斉を討つ事の愚を説き、越に備えるべきだと諫めたが、呉王は聞き入れないばかりか、伍子胥が斉から賄賂を貰っているのではないかと疑うようになった。越の息がかかった伯嚭もしきりに伍子胥を讒言したので、とうとう呉王は伍子胥を殺すことにした。伍子胥の元には、属鏤という名剣が送られた。せめてもの名誉に、この剣で自害しろというのである。
「呉に背いている讒臣は伯嚭だというのに、王は讒言を信じて私を殺そうとは。王僚を暗殺して、当時の公子光を王としたのは、誰の功なのか。そもそも私がいなければ、王になることすらできなかったのに」
そして、属鏤を持ってきた使者に対して、こう言った。
「私の墓の上には梓の木を植えろ。私の死体を肥料としてその木を育て、呉王を入れる棺桶を作れるようにしよう。また、私の眼球をえぐり取って、呉の東門に置け。私はそこから、越が呉を攻め滅ぼすのを、見守るとしよう。」
そう言って、伍子胥は自ら首を刎ねた。
呉が会稽山で越を降してから十二年目の春の事である。呉王夫差は、杞の黄池に諸侯を集めて、諸侯の王になろうとしていた。精鋭の兵はみな黄池に行き、呉を守っているのは太子の友だけであった。越はこの機に四万の兵を引き連れて呉に攻め入った。太子友はあっけなく殺された。夫差がこの知らせを聞いたとき、黄池の会はまだ終わっていなかった。夫差はひたすらこのことを隠して、諸侯との盟約を終え、すぐに呉に帰った。夫差は事ここにいたって、伍子胥の言の正しかったことを知った。越の兵は強く、自軍の兵は長年の戦に疲れていた。夫差は和を求めた。越としてもまだ、呉を滅ぼすには力が足りず、和に応じた。
その四年後、越は再び呉を攻めた。その勢いはすさまじく、各地の呉軍を打ち破り、ついに呉王夫差を、姑蘇城に包囲した。夫差は大夫の公孫雄を使者として遣わし、和を請うた。勾践は哀れに思い、臣下とすることで許そうとしたが、范蠡が夫差の轍を踏むべからずと反対した。勾践は自ら使者に否と告げるに忍び得ず、使者には范蠡をもって否と言わせた。しかし、やはり一時は自分と同じ境遇に落ちただけに、殺すことは忍ばれた。そこで、せめて夫差を甬東へ移し、百家を与えて、余生を送らせようとした。しかし夫差は、いまさら臣下として生き恥をさらすに堪えず、また讒臣の言まどわされて、伍子胥を殺したことを恥じ、自ら命を絶った。「伍子胥に会わせる顔が無い」と、布で顔を覆い隠して死んだと言う。勾践は夫差を厚く葬り、また伯嚭は、不忠の臣として、殺したという。
ああ、文章を書くこと、中島敦のようには、いかないものだなぁ。やはり幼少の頃から、漢文の素読というものが必要らしい。
参考:
十八史略、史記、河出書房出版の新十八史略。