2015-12-30

今年遊んだビデオゲームの感想

今年はFallout 4が発売されたため、久しぶりに大金を投じてゲーム用PC環境を構築した。今年遊んだゲームの感想を書いてみる。

何か他にも面白いゲームがあれば教えてほしい。筆者は一人称シューターが好きだ。ステルス要素は嫌いだ。Ubisoftのゲームはグラフィックだけの見せかけで肝心のゲームは中途半端で、かつUPlayというクソみたいなDRMがあるので避けることにしている。

Borderlands 2

FPS版Diabloといったゲームだ。レベルがあり、敵と武器の強さがレベルによってスケールする。武器の強さは乱数によってさらに増減する。前作と違い、ストーリーもなかなかよい。特に、ハンサムジャックという敵がとてもよい味を出している。サイドミッションにもボイスとストーリーが付いている。とても中毒性のあるゲームだ。

ただ、前作と比べて、とても死にやすいバランスになっている。

そして、ラスボスとDLCのラスボスが極めて退屈だ。ボスは巨体で弱い攻撃をしてくるだけなので、死にはしないのだが、HPが異常に高く、倒すのに時間がかかる上に退屈だ。

Borderlands: The Pre-Sequel

時系列的にはBorderlands 2の少し前のお話。ヒーローを目指すハンサムジャックが少しづつねじれていく。

基本的にはBorderlands 2のシステムを踏襲している。開発が2Kオーストラリアなので、オーストラリアネタが多い。特に、意図的にひどいオーストラリアのなまりが聞き取りにくい。

基本的なシステムは前作と同じなのだが、前作よりも改良されている部分もある。まず、Slag要素がない。B2はSlagがあるために、敵の硬さがSlagを前提に設定されていて、とにかく敵がタフだった。今作はSlagがないので、敵が柔らかめになっている。

Deus EX: Human REvolution - Director's Cut

悪くないゲームだとは思うが、退屈なステレスFPSで、筆者の好みのジャンルではない。

Duke Nukem Forever

開発に13年もかかった大作。あまりの発売延期に、Vaporware Awardを毎年もらっていた伝説のゲーム。

ただし、FPSゲームではなくて、パズルゲームだった。あまりにも期待されすぎたゲームではあるが、実際のところは、悪くないパズルゲームだ。10時間ぐらいは遊べるので、セール時に数百円で買えば納得できる。

なぜ13年も開発が続いたかというと、金があったからだ。Duke Nukem 3Dというゲームの売上と、そのゲームエンジンのライセンス料のおかげで、デベロッパーである3D Realmsにはうなるほど金があった。そのせいで、パブリッシャーは何も言うことができなかったのだ。

一般に、このような規模のゲーム開発では、パブリッシャーが出資をしてデベロッパーが開発する。デベロッパーはゲームの質を高めるために努力するが、パブリッシャーは利益を追求する。そのため、度々デベロッパーとパブリッシャーの対立が起こるものである。最終的に利益を出さなければならない以上、パブリッシャーは往々にして、「品質はそこそこでいいから早くゲームを出荷して利益を出せ」と迫るものである。

たまに非難されるデベロッパーとパブリッシャーの対立を完全に失くした一例がこれだ。結局、現実的な規模のゲームを具体的な納期を区切って開発しないと、際限なく開発が進み、そうこうしているうちに、コンピューターの性能が上がり、ソフトウェアも改良され、結局作り直しを余儀なくされる。

ディレクターのGeorge Broussardが新しいゲームをするたびに、そのゲームの要素をDNFに追加しろと要求するので、開発者の間では、Broussardに新しいゲームをプレイさせるなとまで言われたらしい。

The Elder Scrolls IV: Oblivion

昔懐かしいゲーム。当時何百時間も遊んだはずだ。

The Elder Scrolls V: Skyrim

時が経つのは早いもので、なんともう5年も前のゲームだ。神ゲーである。

Fallout 3

神ゲー。

Fallout 4

確かに、確実に改良されてはいる。特に、コンテナーからルートするときにゲームがポーズしないのはすばらしい。

しかし、問題も多い。UIが全面的にクソだ。Pipboyにこだわるせいで、インベントリ画面は、実画面の数割程度の領域しか使っていない。かつ、OblivionでMODが出たため、Fallout 3以降に取り入れられたKey ringがなくなっている。ホロテープと鍵とその他の雑多なアイテムが、すべてMISCというカテゴリに打ち込まれてしまい、さっき拾ったばかりのホロテープを再生するために数百個ものアイテムの中から目grepする必要がある。一体何を考えているのかわからない。

しかも、クエストが終わったあとも、一部のクエストアイテムがクエストアイテムのままになっていて、インベントリから出すことができない。これもMISC欄を圧迫する。

メインクエストは、New Vegas風に複数の勢力のどれかに属して分岐するようになっているが、大半のプレイヤーは寄り道のため、当初の廃墟を旅する目的はとっくに忘れ去っているだろう。

クエストの大半は印象に残らないが、唯一Silver Shroudだけが面白かった。

コンテンツ不足のため、100時間ほどプレイすると飽きる。

Fallout New Vegas

ほぼFallout 3の古臭いシステム上に作られているため、様々な制約があるが、クエストには興味深いものが多い。戦闘がつまらない。

Hard Reset

あの伝説のPain Killerを開発したPeople Can Flyの元開発者達が立ち上げたFlying Wild Hogの開発したオールドスクールシューター。神ゲー。

Keep Talking and Nobody Explodes

ビデオゲームというよりはボードゲームに近い。ビデオゲームとしては、爆弾を解除するゲームだ。爆弾の解除方法はマニュアルに記載されているが、コンピューターを操作して爆弾を解除する人間は、マニュアルを読むことができず、マニュアルを読む人間は画面を見ることができない。爆弾を解除する人間とマニュアルを読む人間は、口頭でやり取りをする。マニュアルは極めて複雑で伝達ミスによる失敗を生みやすい。極めて面白いゲームだ。ゲーム実況にも向いている。

Kerbal Space Program

ロケットを組み立てて飛ばす極めて難しいゲーム。筆者はまだ軌道にのせることすらできていない。

Metro 2033 Redux

シューターとしては微妙だが、世界観は悪くないゲーム。舞台は核戦争後のロシアで、地上は汚染されているため、人々は地下鉄のトンネルの中に住んでいる。通貨はミリタリーグレードの実弾だ。問題は、ストーリーが超自然的でそれほど面白くはないことか。

Metro Last Light Redux

前作のバッドエンドから続くストーリー。システムはほぼ同じ。こんども世界観は悪くはない。前作よりも拠点での生活感が感じられる。しかし、やはり文明崩壊後の世界にしては文明が残りすぎている。まともな生産設備もないくせに、なぜシームレスストッキングがあるのかとか、冷静に考えて食うものすら満足に作れないはずなのに衣食住と武器がやたらと豊富だったり、また狭い地下鉄内でナチ党と共産党の軍隊が戦争をしていたり、相変わらずストーリーは超自然的な存在との対話を軸にしたものであったりと、プレイヤー置いてけぼりの電波ストーリーとなっている。

Painkiller: Black Edition

あのPeople Can Flyが開発した神ゲー。

Painkiller Hell & Damnation

Painkillerのリメイク。バニーホッピングを再現している。なぜか時系列的には過去の5作の後のようだ。

POSTAL 2

トレーラーハウスに住む昨日ゲーム会社に就職したばかりの主人公、Dudeが職場に行って給料を受け取ったり、ミルクを買ったりする極めて平和的なお使いゲーム。神ゲー。最近、Paradice Lostという拡張も出たので、ひさしぶりにプレイするにも最適だ。

Serius Sam 3: BFE

Serious Samの伝統に忠実なオールドスクールシューター。ただし、後半のステージがクリアできない。

Serious Sam HD: The First Encounter

Serious Sam第一作のリメイク。理不尽な初見殺しのトラップが多い。

Shadow Warrior

あのPainKillerを開発したPeople Can Flyから離脱した元開発者達が立ち上げた、Hard Restも開発したFlying Wild Hogが開発したゲーム。今回はシューターというよりもmelee重視のゲームになっている。神ゲー。

A Story About My Uncle

Grappling hookを用いた3Dプラットフォーム。面白いのだが、最後の方の極端に難しい場所が進めずに止まっている。

購入を検討しているゲーム。

Portal

極めて有名なゲームだが、あまり3Dパズルプラットフォームは好きではないのでやる気にならない。

Dying Light

面白そうなのだが、戦闘が単調で10時間ぐらいで飽きそうだ。

Just Cause 3

パフォーマンスに問題を抱えている上、ランキングシステムのために常にサーバーと接続していて、コネクションはたびたびブチ切れ、ゲームメニューを開いただけで何分も接続待ちをするので、Steamをオフラインモードにした上でファイヤーウォールで外向きのパケットを遮断するworkaroundが取られているというクソな実装だと聞いている。「明らかに、地面のテクスチャはnVidia専用らしいぜ」とか、「俺のババアのほうがもう少しは速い」などという名言レビューを生み出したおそらく今年最大のガッカリゲー。

サバイバルクラフト系ゲーム

節操無く乱立していて、どれもEary Access。どのゲームもシステム上、Spawn Camperばかりになるようだ。

2015-12-29

AVGのクソみたいなChrome拡張の脆弱性

Issue 675 - google-security-research - AVG: "Web TuneUP" extension multiple critical vulnerabilities - Google Security Research - Google Project Hosting

アンチマルウェアソフトウェアのAVGが、クソみたいな脆弱性を含むChrome拡張を、Chrome拡張のインストールを阻止する仕組みを意図的に迂回して無理やり入れた挙句、脆弱性を生み出していたそうだ。しかも、脆弱性の指摘に対する修正案がお粗末すぎる。このようなセキュリティ的にお粗末な対応をするところが出しているセキュリティ用のソフトウェアは一切信用できない。読者の中にAVGを利用しているものがいたら、即刻に消すべきだろう。

ユーザーがAVG AntiVirusをインストールすると、"AVG Web TuneUp"という拡張idがchfdnecihphmhljaaejmgoiahnihplgnのChrome拡張が無理やりインストールされる。webstoreの統計によると、900万人のアクティブChromeユーザーがこの拡張を有効にしている。

この拡張は様々なJavaScript APIをChromeに追加する。どうやら、これによって、拡張は検索設定や新しいタブページをハイジャックできる。インストール方法はとても複雑で、このような拡張APIの悪用を防ぐために設けられたchromeのマルウェアチェックを迂回している。

とにかく、APIの多くがぶっ壊れている。添付した攻撃サンプルは avg.comのcookieを盗むことができる。また、閲覧履歴などの個人情報をインターネット上に公開させることができる。任意のコード実行につながるような脆弱性が含まれていてもおかしくはない。

Chromeの開発者は、怒りのメールをAVGに投げるが、返ってきたAVGの主張する「修正」は、拡張コードの実行を、以下のような条件で制約をかけるものであった。

var match = event.origin.match(/https?:\/\/.*\.avg\.com/i);

if (match ! null {
...
}

このコードの意図は、拡張の動作をAVGのoriginのみに制限するものだが、正規表現は極めて悪く書かれていて、例えば、"https://www.avg.com.www.attacker.com"のようなoriginも受け付けてしまう。かつ、AVGのWebサイトにXSS脆弱性が存在した場合、誰でも拡張によってもたらされた「機能」を利用できてしまう。

Chrome開発者がそのような返答メールを返したところ、こんどは、拡張の動作を"mysearch.avg.com" and "webtuneup.avg.com"をoriginとする場合のみに制限するとんちんかんな「修正」を送ってきた。これ以上改善の見込みがないと判断したChrome開発者は、以下のようなメールを投げつける。

見てみたが、この方法は動くだろう。ただし、ホワイトリストしたドメインがXSSやmixed contentを含む場合、誰でも脆弱性を活用できてしまう。

残念なことに、そういう脆弱性を発見するのは難しくない。私はWebセキュリティ専門家ではないが、数分探すだけで、以下を見つけた。

http://webtuneup.avg.com/static/dist/app/4.0.5.0/interstitial.html?risk=%3Cimg%20src=x%20onerror=alert(1)%3E&searchParams=%7B%22lang%22%3A%22en%22%2C%22pid%22%3A%22pid%22%2C%22v%22%3A%22vv%22%7D

わかりやすく説明してやると、このドメイン下にXSSバグが存在する限り、すべてのAVGユーザーは、銀行、メール、その他すべての個人情報が流出してしまう。そのため、このドメイン下は入念に保守と検証をする必要がある。

御社はホワイトリストされたドメインに対してプロのWebセキュリティ検証を行うという条件下で、この修正は動くだろうよ。

セキュリティ用のツールを開発しているところがこんなマヌケな「修正」を送ってよこすとは。AVGは一切信用できない。ちなみに、avg.comのXSS脆弱性は、未だに修正されていない。

思うに、もはやアンチマルウェアソフトウェアの時代は終わった。というのも、結局既知のマルウェアに特有のシグネチャを検出するのは何の訳にも絶たない。マルウェアを作り出すのはそれほど難しくはないからだ。マルウェアに特有の挙動を仮想環境上で動かして調べるというのも、誤爆が多く役に立たない。

それどころか、アンチマルウェアソフトウェアの殆どは、カーネルモードで動作するコードを追加したり、カーネルAPIをフックしたり、カーネルを書きかえたりする。それによって、新たな脆弱性を追加している。追加のソフトウェアは追加のリスクという古き良き教訓は忘れ去られてしまったのだろうか。

それに、この拡張によって一体どのようなセキュリティが確保されるというのか。筆者には、AVGが検索クエリーなどの個人情報を収集して販売するための機能にしか思えない。すると、AVGはマルウェアにほかならない。

このマッポーの世の中では年末といえどもまともなオーガニック・スシを食べることすらかなわない

年末なので贅沢にもツキジでコンベヤーベルト完備ではない高級オーガニック・スシをつまもうと出かけたが、このマッポーの世の中では、ニコチン中毒者によるキセル・パイプの害のないスシ・レストランは存在しなかった。

大抵のスシ・レストランは、エントランスにはいった瞬間にシガレット臭いアトモスフィアが流れてくる。とてもではないがオーガニック・スシを出すレストランだとは思えない。

落胆して、マケグミ・サラリマンのよく立ち寄る禁煙が保証されている合成ソバショップに入りかけたが、おお、ゴウランガ! とあるスシ・レストランの店先に、「カウンター 終日禁煙席」とショドーされているではないか。スシを補給するのにこれ以上の場所はあるまい。さっそく中に入り、カウンターチェアに腰掛けた。

しかしなぜか流れてくる煙たいアトモスフィア。振り返れば、タタミ一枚も離れていない距離にいる典型的なニコチン中毒者フェイスをしたサラリマンがタバコ・ドラッグをキメているではないか。看板に偽りあり!

筆者が普段外食を避けている理由はこれだ。このマッポーのトーキョーではレストランはニコチン中毒者の巣窟なのだ。ニコチンスモークが蔓延するアトモスフィアのレストランでまともなオーガニック・スシなど期待できるわけがない。

サラリマンの身分とはいえ、年末ぐらい贅沢をしたかったのだが、このマッポーの世の中では贅沢に値するオーガニック・スシ・レストランなど存在しないようだ。

2015-12-22

Linuxカーネル、Rockchip暦に対応

kernel/git/torvalds/linux.git - Linux kernel source tree

Linuxカーネルにあふれる文才と皮肉の無駄遣いを感じるコミットメッセージがある。

西暦1582年、ローマ教皇、グレゴリウス十三世は既存のユリウス暦が現実を十分に正しく表現していないことを見出され、不足分を補うために、うるう年を計算する規則を変更なされた。同様にして、西暦2013年に、Rockchipのハードウェアのエンジニアは、新しいグレゴリオ暦がまだ誤りを含むことを見出した。すなわち、11月は31日まで存在するよう改めた。遺憾ながら、暦の変更が広く浸透するには時間がかかる。先のプロテスタント国家がグレゴリウスの発案を受け入れてから、まだ300年しかたっておらず、すべての宗教とオペレーティングシステムカーネルがRockchip暦の改良を受け入れるにはまだ長年を待たねばならない。その時に至るまで、我々はRockchipのハードウェアから読み、そして書き込む日付情報を、グレゴリオ暦に変換する必要がある。

このパッチは、2016年1月1日を、Rockchip暦とグレゴリオ暦が同期している起点として定める。この起点から、起点移行の任意の日付を、11月から12月への変遷を何度経たかを数えて、2つの暦のオフセットを計算し、相互変換する。ハードウェアの日付を日常的に変更する方法ではなく、この方法を選んだ理由は、システムが不明な年数シャットダウンされたとしても日付を維持できる唯一の方法だからである。欠点は、同じハードウェアの他のソフトウェア(メインボードのファームウェアなど)も、RTCから正しいタイムスタンプを読み書きするために、同じ変換方法(起点も同じ)に従わなければならない。

短い翻訳:ロックチップのハードウェア内蔵の時計が、不具合により、11月は31日まであると認識しているので、存在しない11月31日が存在し、またそれにより毎年一日づつ現実のまともなグレゴリオ暦からずれていく。この問題をLinuxカーネル側で静かに修正するためのパッチ。

2015-12-21

ドワンゴにおける業務外Slackチャンネルのまとめ

はてな社内Slackでウォッチしている非業務チャンネル6選 - 平常運転

ドワンゴ社内Slackの非業務チャンネルを紹介 - ゆっくりしてない

ドワンゴ社内チャットにおける各言語別チャンネルの参加者数をグラフで表してみた:dwango エンジニア ブロマガ:ドワンゴ研究開発チャンネル(ドワンゴグループのエンジニア) - ニコニコチャンネル:生活

すでにまとめられているので三番煎じだが、ドワンゴにおけるSlackの業務外チャンネルをまとめてみた。

ドワンゴには今、1000人ぐらいの被雇用者がいるはずであるが、現時点でチャンネル数は1534ある。ゆっくりがまとめた19日より、わずか2日間で7個増えている。

#4gamer

チャンネルの由来は4Gamer.netだろうか。ビデオゲームについて話すチャンネル。

#boardgame

ボードゲームのチャンネル

#kusoge

クソゲの雑談をするチャンネルとしてはやらせようとしたが発言がない。

他にも、#e-sportsやら#pokerやら、ゲームに関するチャンネルが多い。個別のゲーム用のチャンネルが多数ある。

#boulder

#anime

アニメについて論ずるチャンネル

ボルダリング部のチャンネル

野球部やテニス部やダンス部などのチャンネルもあると思う。

#rookies2014 - 2014年新卒が多く集まるチャンネル
#rookies2015 - 2015新卒が多く集まるチャンネル
#rookies2016 - 2016新卒が多く集まる予定のチャンネル
#rookeis4015 - 4015年新卒が多く集まる予定のチャンネル(現在確認できる最も未来のためのチャンネル)

一時期、社内でゴミのような人間が#rookies20xxをひたすら増殖したことがあった。#rookies2099もあった気がするが、その時までドワンゴが存続しているのか、また存続していたとしても、現社員が生き残っているかは不明だ。

#drunk, #sake

飲み屋や酒について語るチャンネル。飲兵衛が多い。

#dev_null

どうでもいい技術的な雑談チャンネル

#unk

どうでもいい雑談チャンネル

#security

脆弱性のニュースがよく話題になる。

#yami

社員同士の不要品の闇取引所

#okane_nai

万年金欠気味のドワンゴ社員のたまり場。給料日前には特に発言が増える傾向にある。ゴミのような人間も入っている。

#okane_hontoni_nai

お金が本当にない人が入るチャンネル。ゴミのような人間も入っている。

#shakkin_aru

借金のある人が入るチャンネル。理由は奨学金からカードローンまで様々。家のローンのある人は話題にならないようだ。

#okane_aru

お金のある人が入るチャンネル。発言数は少ない。前回の発言は10月6日。ちなみに、#okane_naiは本記事執筆時点で発言がどんどん増えていく。Slackにおける発言数の多さと貧乏には相関性がある気がする。

#ossan

PC-98とかの話を得意げに語りだすと誘導されるチャンネル。オッサンにしか通じない雑談が多い。ちなみに、#obahanはない。

#office_love

残念ながらこのチャンネルで成立したカップルはまだ存在しない。

#office_live

かいしゃぐらし。残念ながらドワンゴにはシャワー室がなく、寝る場所も確保しにくいので暮らしにくい。

#kanojo_inai

「最終目標はこのチャンネルが不要になること」というトピックがすべてを物語っている。

#karesi_inai

現在彼氏のいない女性社員が男の出会いを求めて入るチャンネル・・・ではなく、単に野郎がわずかに入っているだけのチャンネル。ゴミのような人間にすら見放された。

#diversity

「思想・国籍・障害・人種・性別・年齢・価値観等の多様性と、会社や社外活動との関係について、幅広く考えるチャンネルです」とのこと。最近では夫婦同姓の強制に対する合憲判決や、女性の再婚六ヶ月禁止規定の300日超は民法の矛盾から無効判決などが話題になった。

#c_plus_plus

C++の雑談。

#perl

ドワンゴのslackにおけるプログラミング言語別チャンネルまとめ記事のあとに作られたチャンネル。参加者は少なく発言もない。

#lisp

トピックが「哲学」になっている。

その他、主要なプログラミング言語ごとにチャンネルが乱立している。

#kickstarter

kickstarterや、いかにもクラウドファンディングで出されそうなガジェットの雑談が多いようだ。

#free

自由について語るチャンネル。過去に自由ソフトウェア財団のグッズを共同購入したりした。

#*****

チャーハンを愛する人たちが大盛りチャーハンに挑戦するチャンネル。実質係数という独自の用語がとび交う。

#lunchpassport

ランチパスポートを活用する人たちが集うチャンネル

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

C++標準化委員会の文書のレビュー: P0130R0-P046R0

P0130R0: Comparing virtual functions

記述が貧弱だが、2つのクラスのオブジェクトの指すvirtual関数が同一のものかどうかを比較する機能が欲しいそうだ。


struct A
{
    virtual void vf() { }
} ;

struct B : A
{
    void vf() { }
} ;

struct C : A
{
    
} ;

void f( A * a1, A * a2 )
{
    // a1.vfとa2.vfが同じvirtual関数を指しているかどうか知りたい。
}

論文はC#にメソッドを比較する方法があることを挙げて、C++にも同等機能がほしいとしている。

論文著者は、どうやらEAのゲーム開発者のようだ。

[PDF] P0131R0: Unified call syntax concerns

Unified Call Syntaxに持ち上がっている懸念の考察と払拭。Bjarne Stroustrupが著者。

まず、Unified Call Syntaxとは、x.f(y)という式があったときに、まずxのクラスの呼び出せるメンバーfを探し、妥当なものが見つからなかった場合は、f(x,y)として呼び出せる関数fを探す。同様に、f(x,y)という式があったときに、まず呼び出せる関数fを探し、妥当なものが見つからなかった場合は、x.f(y)として呼び出せるか試す。

これによる利点としては、呼び出し側は、ライブラリがメンバー関数で実装しているのかフリー関数で実装しているのか気にする必要がなくなるので、より簡単に、より汎用的なコードが書ける。

例えば以下のような問題がある。

struct API
{
    void member( int ) ;
} ;

void user_code( API & api )
{
    // 呼び出し前に引数のチェックする拡張
    auto checked_member = []( API & api, int i )
        {
            assert( i > 0 ) ;
            api.member( i ) ;
        } ;

    api.checked_member( 123 ) ;
}

このようにユーザーが既存のクラスを拡張したとする。これはUnified Call Syntaxで想定されている使い方の一つだ。しかし、APIが後にchecked_memberを付け加えたらどうか。

struct API
{
    void member( int ) ;
    void checked_member( int ) ;
} ;

ユーザーコードの意味が変わってしまう。

Bjarne Stroustrupは、このような問題は珍しいし、我々は何十年もこのような問題に満足に[要出典]対処してきたし、実装は警告できるし、インターフェースを雑に拡張すべきではないし、名前空間を使えば問題は回避できるし、他の言語も似たような問題はあるが、それほど深刻な問題ではないとしている。

もうひとつ、テンプレートのルックアップの問題がある。

template < typename T >
struct X
{
    void f( T & t )
    {
        t.m() ;
        m(t) ;
    }
} ;

テンプレートの名前解決には宣言時に解決するものと、実体化時に解決するものがある。ADLは実体化時に解決する場合でないと働かない。m(t)の場合には、tは依存名でmはまだ宣言されていないので、ADLが働く。t.m()の場合、そもそもADLの働く余地がない。t.m()が実体化時に呼び出せないとわかった時には、すでに宣言時に解決されているので遅すぎる。

この問題は、t.m()に対して、宣言時にm(t)でも二重に名前解決をしてから、宣言時と実体化時のどちらで名前解決をするのか決定する

IDEについてさらっと流されているのが興味深い。IDEにとって、Unified Call Syntaxをまともに静的解析による候補表示に適用すると候補が多すぎて便利にならない問題が考察されていない。

P0132R0: Non-throwing container operations

無例外版のコンテナー操作を追加する提案。

世の中には、数百キロバイトしかRAMを積んでいない組み込み環境がある。そのような極端な環境でも、ネットワークもXMLパースもUIフレームワークもスクリプトもすべてやらなければならない環境がある。そのような環境では、例外のコストは支払うことができない。

このような組み込み環境では、メモリは貴重であり、固定サイズのメモリを保持し続けることは禁忌である。動的メモリ確保は失敗する可能性が日常的にあり、メモリ確保に失敗した時の対処方法は、直ちに終了することではない。

そのような組み込み環境でもC++の恩恵は受けたいと思っており、近年、標準C++に近づきたい要望がある。しかし、そのためには例外や、メモリ確保失敗時には原則直ちに終了するような設計の現在のコンテナーは使えない。

如何にして既存のコンテナーに無例外版の操作を追加するかということについて、いろいろと案があるが、どれも一長一短だ。

無例外版のオーバーロードを追加する案

bool push_back(nothrow_t, const T&);    
bool push_back(nothrow_t, T&&);   

別の名前を使う方法

bool push_back_nothrow(const T&);    
bool push_back_nothrow(T&&);   

アロケーターに例外の代わりにnullptrを返せるようにし、nullptrを返した時のコンテナーの挙動を規定する方法

アロケーターにメモリ確保失敗時に呼び出すコールバック関数を設定できる機能を追加する方法

コンテナーの特殊化を追加する方法

時間がかかりそうだ。

P0133R0: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0133r0.html

noexcept(auto)の保留にすると宣言した短い文書。

極めて一部の例にしか適用できないため。

P0134R0: Introducing a name for brace-or-equal-initializers for non-static data members

非staticデータメンバーに初期化子を書けるようになったが、この機能に対して名前が与えられていない。

struct S
{
    int x = 0 ;
} ;

現行の規格では、この機能は"brace-or-equal-initializer for a non-static data member"と言うことができる。

名前がないのは不便なので、デフォルトメンバー初期化子(default member initializer)という名前を与える提案。規格の文面もこの用語を使ったものに書き変える。

P0135R0: Guaranteed copy elision through simplified value categories

value categoryを変更し、コピー省略を規格で強制する提案。

新しいvalue categoryの定義では、glvalueはオブジェクトやビットフィールドや関数といった場所に対する計算で、prvalueはオブジェクトやビットフィールドや関数を初期化するための評価となる。

そして、以下のような例で、コピー省略を必須の挙動にする。


struct X
{
    int data ;
} ;

X f() { return X{123} ; }

X x = f() ; // コピー省略は必須

このようなコードは、たいていの実装でコピー省略ができる。しかし、依然としてコピーコンストラクターは呼び出し可能でなければならない。ユーザーもコンパイラーもコピー省略ができると了解しているコードでコピーコンストラクターの存在が必要になるのは変だ。Xがコピーもムーブもできない型の場合、型システムの都合上、動的確保せざるを得なくなる。

P0136R0: Rewording inheriting constructors (core issue 1941 et al)

継承コンストラクターの文面をわかりやすい定義に変更する提案。定義の変更により、継承コンストラクターの挙動に些細な違いが生じるが、概ね改良である。

Core Issue 1776: Replacement of class objects containing reference members

ややこしい型システム上の文面の変更、std::launderの追加。

P0138R0: Construction Rules for enum class Values

scoped enumを使ったstrong typedefの提案。

enumeratorの存在せず内部型が指定されているscoped enumは、リスト初期化を使うと縮小変換が起こらない限り暗黙に型変換できる。というルールを追加する。

// 内部型の指定がある
// enumeratorが存在しない
// scoped enum
enum struct Index : std::uint32_t { } ;

void f( Index index ) ;

f( { 1 } ) ; // OK

これにより、勝手に整数型に暗黙に型変換されず、またリスト初期化を使わない限り整数型で初期化できない、強い整数のエイリアスを作ることができる。

整数以外の型には使えない。

より一般的な強いtypedefとして、opaque alias提案がある。

P0146R0: Regular Void

void型を完全形にする提案。

結果として、void型の変数が作れるし、void型の引数が取れるし、void型のリファレンスも作れる。

void a ;
void & b = a ;
void f( void, void ) ;
f( a, a ) ;
void c[5] ;

このような変更をする動機は、テンプレートコードでの汎用性を高めるためだ。現在、voidは不完全型であり、テンプレートコードはvoidのためだけに特殊化を書かなければならない。voidが完全形になれば、そのような問題はなくなる、

互換性の問題は、それほど大きくないと考えられている。多くのユーザーはvoidが完全形になっても気が付かないだろう。ただし、いくつか問題はある。

現在、void型ひとつだけを引数に取る関数は、引数を取らない関数という文法的意味が与えられている。このため、依存名ではないvoidが単一で引数に使われていた場合、引数を取らない関数という意味にする例外的なルールが必要になる。論文では、いずれはこの文法を廃止したほうがよいとしている。

void型を完全形にしたことによるABI互換の崩れはない。なぜならば、void型は状態を持たないためである。

sizeof(void)が違法であることを利用したSFINAEは、現時点で広く使われていないので、それほど問題にならない見込みだ。

条件演算子の評価結果がvoidになる場合があるが、これについては互換性に対する詳細な考察が必要である。

sizeof(void)が0を返すと最適化に使えるが、しかし、その場合voidの配列が作れない。sizeof(void)は1を返すように規定するのが最善だろうとしている。

提案には文面案も付属している。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

とても賢いコンパイラーの逆襲

The Hacks of Life: The Dangers of Super Smart Compilers

Clangの最適化が未定義の挙動を検出してコード片を消し去ってしまったことに引っかかった開発者の嘆き。

今日初めて、RenderFarmのDSF render(global scenaryを作成するのに使っている内部ツール)をClangで最適化コンパイルして実行した。

結果はsegfaultだった。これは驚きだ(そして自身消失だ)。というのも、最適化していないデバッグビルドは問題なく動くし、GCCでコンパイルされた最適化ビルドも正しく動く。-O0ではバグがない(つまり#if DEVコードのバグではない)ので、「最適化は何をやっているんだ」の時間だ。

大量のprintfと試行錯誤の結果、最適化は以下のようなコード片を丸ごとすっ飛ばしていることが判明した。

for(vector<mesh_mash_vertex_t>::iterator pts = 
   ioBorder.vertices.begin(); pts != 
   ioBorder.vertices.end(); ++pts)
if(pts->buddy == NULL)
{
   /* とても重要な処理 */
}

とても重要な処理はすっ飛ばされていて、実際、とても重要だった。

さて、なぜだ。buddyはポインターではない。スマートハンドルである。そこで、operator ==は単にポインターを比較しているのではない。コードをさらに深く探って見てみよう。ハンドルはポインターのラッパーであった。operator *は*m_ptrを返す。operator ==はnullとの比較が動くように特別に定義されている。

  template < class DSC, bool Const >
  inline
  bool operator==(const CC_iterator<DSC, Const> &rhs,
                  Nullptr_t CGAL_assertion_code(n))
  {
    CGAL_assertion( n == NULL);
    return &*rhs == NULL;
  }

もちろん、Clangは筆者よりとても賢いので、このコードについて物申すことがある。

合法なC++のコードでは、リファレンスはnullポインターを束縛することはできない。比較は常にfalseと評価されると推定できる。

やれやれ、これが問題だ。このoperator ==は、他の多くのコードと同じく、&*を使ってラッパーから生のポインターを得ている。&と*はお互いに打ち消しあうので、生ポインターが得られる。

ただし、Clangはとても賢いので、「ふむ、もし&*rhs == NULLの場合、*rhsはどうなる? NULLリファレンスではないか(rhsがNULLでそれをデリファレンスした場合だ)。そして、NULLリファレンスは違法なので、これは起こりようがない。このコードは*rhsが評価された瞬間に未定義の挙動となる。このコードは未定義の挙動であるからして(*rhsがnullオブジェクトである状況が存在すればだが、そんな状況は存在しない)、コンパイラーは何でもできるぞ! もし、*rhsがnullオブジェクトではないのならば、&*rhsはNULLと同一になることはない。したがって結果はfalseだ。さて、一方がfalseでもう一方が未定義ならば、関数全体を以下のように書きかえられる」

  template < class DSC, bool Const >
  inline
  bool operator==(const CC_iterator<DSC, Const> &rhs,
                  Nullptr_t CGAL_assertion_code(n))
  {
    return false; /* ほら、直してやったぜ */
  }

そして、Clangはまさにこれをしている。 つまり、if(pts->buddy == NULL)がif(false)になったので、重要な処理は絶対に実行されない。短期的な修正は以下だ。

for(vector<mesh_mash_vertex_t>::iterator pts = 
   ioBorder.vertices.begin(); pts != 
   ioBorder.vertices.end(); ++pts)
if(pts->buddy == CDT::Vertex_handle())
{
   /* do really important stuff */
}

これで、operator ==は2つのハンドルを比較するものが使われる。

  template < class DSC, bool Const1, bool Const2 >
  inline
  bool operator!=(const CC_iterator<DSC, Const1> &rhs,
                  const CC_iterator<DSC, Const2> &lhs)
  {
    return &*rhs != &*lhs;
  }

これも違法な未定義の挙動なのだが(&*をnullポインターに使うのは違法)、Clangは気が付かないようで、最適化はこのコードを消せない。このコードはポインター比較になった。我々の勝ちだ。

新しいバージョンのCGALはこの問題を修正していて、operator ->()が生ポインターを返すのでそちらをつかうようになっている。

Clangの援護をすると、プログラムの実行時間はseffaultを起こすまでは確かに早かった。

すべてのライブラリを最新版にアップデートしないことを笑うかもしれないが、3つか4つぐらいのコンパイラーやビルドシステムを使っている環境でライブラリをアップデートして、動かなかった場合の依存関係を全部解決するのは難しいので、とりあえず問題を解決した我々を糾弾しないでくれ。

2015-12-20

HTTPステータスコード451(政治的な検閲)が正式に承認される

mnot’s blog: Why 451?

draft-ietf-httpbis-legally-restricted-status-04

HTTPステータスコード451がIETFで正式に承認された。近いうちにRFCとしても発行される。

元ネタは、Ray BradburyのFahrenheit 451(華氏451)というタイトルの小説で、これはディストピアな検閲社会を描いている。

451の意味は、403(禁止/権限がない)と似ているが、正確な意味は、ドラフトを引用すると、以下の通り。

このドキュメントはサーバーオペレーターが、あるリソース、あるいはあるリソースを含むリソース群に対し、閲覧を検閲するよう法的な命令を受け取った時に使うHypertext Transfer Protocol(HTTP)ステータスコードを規定するものである。

このステータスコードは、法律や一般大衆の雰囲気がサーバーの運営に影響をもたらした時に透明性を高める情報開示のために使うことができる。この透明性はオペレーターとエンドユーザーにとって有益なものとすることができる。

RFC4924はインターネットの透明性を抑圧する勢力について考察している。その勢力にはコンテンツへのアクセスを禁止する法的な介入が含まれることは明らかである。参照先のドキュメントによる記述や、RFC4084のセクション4がしめす通り、そのような検閲の事実は公開されるべきである。

HTTPステータスコード451の採用には、結構な議論があったらしい。もともと提案したTim Brayと同志の者は、オンライン上における検閲の事実は開示すべきであると考えていた。403は単に「禁止されている」という意味だけで、「法的な理由により閲覧させることができない」という意味はない。

当初、IESGでは反対する委員も多かったようだ。というのも、HTTPステータスコードは限りある名前空間だからだ。400から499までしかなく、全てに意味が割り当てられてしまったあとは、もうどうしようもない。

とはいえ、賛同する委員も多く、また検閲事実の収集を自動化したいという声もあったため、採用に至った。

451はどのように使われるのか。すべての検閲が451を使うことは期待できない。451はネットワーク検閲フィルターの役割を果たすファイヤーウォールが使うことも考えられるが、おそらく現実的には、発信元のWebサーバーが使うだろう。Github, Twitter, Facebook, GoogleといったWebサイトはしばしば検閲を要求されている。

抑圧的な国家は、検閲されている事実をも検閲するため、451を返すこと自体が検閲されるだろう。その場合、市民は国家が自国民を検閲しているという強力なメッセージを受け取ることになるだろう。

2015-12-18

やねうらおが本当に必要だったもの

range-based forはコンピューター将棋で使えるのか? | やねうら王 公式サイト

やねうらおが、range-based forが使えないとこぼしている。しかしその利用例をみるに、そもそもrange-based forを使うべきではない。

range-based forは、イテレーターというコンセプト(まだコンセプト機能はC++にないが)に従っている。イテレーターはポインター操作をモデルとしている。

  • イテレーターは要素群の中のある要素を指していて、operator *で要素を参照できる。
  • イテレーターの指す要素は、operator ++で次の要素に移動できる。
  • イテレーターはhalf-openとなっていて、要素群の最後の要素のひとつ次の、何も指していない状態のイテレーターが存在する。これを番兵のように使い要素の端に到達したことを判定する

一方、やねうらおのコードは、本来イテレーターではないものを無理やりイテレーターのようにしている。Squareというのは、おそらくunscoped enumだ。(C++11ではscoped enumという強い型をもつenumが追加された。従来の弱いenumはunscoped enumと呼ばれる)。つまり整数型と番兵なのだろう。

したがって、やねうらおに必要なのはrange-based forではない。

ところで、記事中でやねうらおはCプリプロセッサーマクロを多用している。これは極めて醜悪だ。C++にはlambda式が存在するので、同等のコードはもっと綺麗に書ける。

おそらく、やねうらおが本当にほしかったのは、こういうものではないのか。

template < typename T, typename Func >
void yaneu_for( T i, T sentinel, Func func )
{
    for ( ; i != sentinel ; ++i )
    {
        func( i ) ;
    }
}


int main()
{
    yaneu_for( ZERO, NB,
        []( auto i )
        {
            // 処理
        } ) ;


    int hoge ;

    yaneu_for( ZERO, NB,
        [&]( auto i )
        {
            // lambda式の外の変数も使える
            hoge = i ;
        } ) ;
}

初期値iと番兵sentinelを指定すると、sentinelに到達するまでインクリメントしつつ関数オブジェクトfuncを呼ぶ。関数オブジェクトはlambda式を使うとその場に書ける。

ところで、range-based for文は、以下のように使う。

for ( for-range-declaration : range )
    statement

これは、以下のように展開される(多少の細部を省略している)

{ // 新しいブロックスコープ

    auto && __range = range ;

    for (   auto __begin = begin-expr,
                 __end = end-expr ;
            __begin != end ;
            ++__begin ) {
        for-range-declaration = *__begin() ;
        statement
     }
}

begin-exprとend-exprは、decltype(__range)の型次第で変わるが、いずれもbegin()/end()に相当する処理である。

具体的な例をしめす。以下のようなコードは、

std::vector<int> v{ 1, 2, 3 } ;
for ( auto i : v )
{
    std::cout << i << std::endl ;
}

以下のように展開される。

std::vector<int> v{ 1, 2, 3 } ;
{
    auto && __range = v ;

    for ( auto __begin = v.begin(), __end = v.end() ;
            __begin != __end ;
            ++__begin )
    {
        auto i = *__begin ;
        std::cout << i << std::endl ;
    }

}

v.end()は一度しか呼ばれない。

以下のような場合、

int a[100] ;
for ( int & i : a )
{
    i = 0 ;   
}

以下のように展開される。

int a[100] ;
{
    auto && __range = a ;
    for (   __begin = a, __end = a + 100 ;
            __begin != end ;
            ++__begin ; )
    {
        int & i = *__begin ;
        i = 0 ;
    }
}

それから、もはや現代でinlineを使う理由が思いつかない。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

Ubuntuのカーネルをアップデートしたらカーネルパニックが起きた時の解決方法

昨日、何気なくUbuntuのアップデートをしたら、カーネルが4.2.0-19-genericから4.2.0-21-genericにアップデートされた。いや、正確にはアップデートする途中でdpkgがエラーを出しているようだ。何やら脳裏に不安がよぎる。

リブートすると、"kernel panic not syncing unable to mount root fs on unkonown-block(0,0)"と表示されて止まってしまう。

仕方がないので、ブート時にShiftを押し続けてgrub2のメニューを出し、アップデート前のカーネルである4.2.0-19を選んでブートした。Ubuntuは、少なくとも一つ前のカーネルは残すので、カーネルをアップデートして、何らかの理由でそのカーネルに不具合がある時は、以前のカーネルを使うことができる。

さて、どうするのか。毎回これでは不便なのでとりあえず問題のある新しいカーネルを消してみることにした。

sudo apt-get purge linux-image-4.2.0-21-generic

ただしこれはlinux-genericのような仮想パッケージも消してしまうらしい。apt-get upgradeしても、新しいカーネルが依存関係にないのでインストールされなくなった。

再びlinux-genericをインストールしてみる。

sudo apt-get install linux_generic

結果、linux-headers-generic, linux-image-genericなどのパッケージがすべて入る。

依存関係を解決するために、言われるままに以下のようにする。

sudo apt-get -f install

その結果、linuxカーネルの4.2.0.21をインストールしようとするが、やはりdpkgが途中で止まって、ブートできない。

エラーメッセージをよく見てみると、どうやら、dpkgがlinux-image-extra-4.2.0-21-genericの.debファイルを展開する際にエラーになっているようだ。カーネルに問題があるのではなく、ダウンロード済みのパッケージファイルが何らかの理由で破損しているのだろうか。

ダウンロード済みのパッケージファイルを削除するには、apt-get cleanを使えばよい。

sudo apt-get clean

この結果、パッケージファイルを再びダウンロードし、今度は正しく動いているようだ。年のためにリブートして確認してみる。リブートできた。

~$ uname -r
4.2.0-21-generic

やれやれ。

2015-12-16

Grub2の認証でバックスペースを28回押すとレスキューコンソールに入れる脆弱性が発見された

Back to 28: Grub2 Authentication Bypass 0-Day

Grub2のバージョン1.98(2009年12月)から、2.02(2015年12月)までにおいて、脆弱性が発見された。

脆弱性はGrub2の認証機能を使っていた場合に、ユーザー名を入力すべきところで、バックスペースを28回入力すると、レスキューコンソールに入れてしまうものだ。これにより、コンピューターに物理アクセスを得ている人間が、Grub2の強力なレスキューコンソール機能を使うことができる。

脆弱性の原因も詳しく書かれていて興味深い。grub2のコードでは、'\b'が入力されるたびに、unsigned型の変数をデクリメントする。この時、アンダーフローをチェックしていない。その変数は配列の添字に渡されて、ゼロが書き込まれる。

結果として、関数のreturn addressを0x0にすることができ、関数の終了時に戻って実行されるアドレス0x0になる。

通常のユーザースペースのプログラムやカーネルの場合、便利な保護機能が何重にもあるので、ここで終わりになるはずだが、grub2はブートローダーなので、そのような便利な保護機能はない。特殊な実行環境の結果、self-modifying codeを含むループに突入し、最終的に、grub_rescue_run()の先頭アドレスに飛ぶことで、レスキューコンソールに至る。

とても面白い。

C++標準化委員会の文書: P0120R0-P0129R0

P0120R0: constexpr unions and common initial sequences

variant<A, B>に対して、variantの実装は、以下のような型を作り出す。

struct storage{
char discriminator;
union {
A a;
B b;
} u;
};

discriminatorは、どの型が現在有効になっているかをしめす変数だ。

しかし、以下のように実装したほうが、メモリ効率が良い。

struct wrapped_A{
char discriminator;
A data;
};
struct wrapped_B{
char discriminator;
B data;
};
union storage{
wrapped_A a;
wrapped_B b;
} u;

AやBのアライン要求がゆるい場合、メモリ効率が良くなる。

しかし、この実装はconstexprにできない。その理由は、現行規格の文面が不必要な制限を課しているからである。その制限を緩和する提案。

[PDF] P0121R0: Working Draft, C++ extensions for Concepts

コンセプトTSのドラフト

[PDF] P0122R0: array_view: bounds-safe views for sequences of objects

連続したストレージを多次元配列のように見せかけるラッパーライブラリ、array_viewの提案。

P0124R0: Linux-Kernel Memory Model

Linuxカーネルのメモリモデルの解説

背景事情としては、Linus Torvaldsから、CとC++が標準化したAtomic操作ライブラリは、設計がクソで、Linuxカーネルでは使うつもりはないと言われたので、Linuxカーネルでも使える設計にするために、まずLinuxカーネルのメモリモデルを詳細にまとめた文書を作成した。

[PDF] P0123R0: Unifying the interfaces of string_view and array_view

string_viewは、std::stringや、null終端されたchar *や、char *と文字数といった異なる連続したストレージ上に構築された文字列表現をラップして、共通の操作を提供してくれるライブラリだ。

array_viewは、連続したストレージ上の配列をラップして、多次元配列として使うことができるライブラリだ。

string_viewとarray_viewは似通っているため、インターフェースを統一したい。いっそ、string_viewはarray_viewのエイリアステンプレートでよい。つまり、以下のようになる。

template<class CharT, size_t Extent = dynamic_range>
using basic_basic_string_view = array_view<CharT, Extent>;

これにより、連続したストレージは同じ方法で扱える。C++プログラマーが覚える必要のあるライブラリが減る。

これまで提案されていたstring_viewは、basic_stringとの互換性のため、findなどの同じメンバー関数を提供していた。この提案では、メンバー関数として提供されることはない。フリー関数を使うべきだ。


std::string s("hello") ;
std::string_view sv("hello") ;

auto pos1 = s.find("ll") ;
auto pos2 = sv.find("ll") ; // エラー

提案されているstring_viewは、null終端されている保証を持たない。null終端された文字列が欲しい場合は、フリー関数ensure_z()に渡すと、null終端された文字列が得られる。これは、文字列リテラルに適用する場合ゼロオーバーヘッド、その他の場合はstrlen相当のコストがかかる。

P0124R0: Linux-Kernel Memory Model

Linuxのメモリーモデルを解説した文書

P0125R0: std::bitset inclusion test methods

std::bitsetは、数学的な集合を表現するのに使うことができる。

bitsetのn個目のbitが1かどうかで、n個目のオブジェクトが存在するかどうかを表現できる。

この提案は、bitsetにinclusionを判定する機能を付け加えるものである。

  • 集合Aに含まれるすべてのオブジェクトが集合Bに含まれる場合、AはBのサブセットである
  • AがBのサブセットであるか、AとBが同一である場合、BはAのスーパーセットである
  • AがBのサブセットで、AとBが同一ではない場合、AはBの真の(あるいは厳格な)サブセットである
  • AがBのサブセットで、AとBが同一ではない場合、BはAの真の(あるいは厳格な)スーパーセットである

この提案は、厳格なサブセットとスーパーセットを判定する以下のようなメンバー関数をbitsetに追加する。

bitset::is_subset_of(const bitset&) ;
bitset::is_superset_of(const bitset&) ;

追加する理由は、明示的に名前が付いていることによる読みやすさのためと最適化のためである。

bitsetのサイズがマルチワードの場合、aがbのサブセットであるかどうかを判定するのに、以下のような実装では、効率が悪い。

std::bitset<256> a, b;
auto a_includes_b(a & b == b);

なぜならば、これはaとbの全ビットを比較してしまうためである。メンバー関数は、結果がfalseであると判明次第処理を戻すことができるし、規格ではそのような最適化をするように規定されている。

auto a_includes_b( a.is_subset_of(b) );

[PDF] P0126R0: std::synchronic<T>

よいスピンロックライブラリ、synchronic<T>の提案

よいスピンロックを書くのは難しい。unmitigated spinning (TTAS), hardware thread yielding, randomized timed exponential back-off, and invocations of platform API’s like SYS_futexを組み合わせなければならない。std::mutexはそのような実装になっていることが多いが、ユーザーが実装するのは難しい。condition variableはその使われ方と歴史的経緯から、直接プラットフォームAPIを呼ぶ実装になっていることが多い。

synchronicは以下のような宣言になっている。

template <class T>
struct synchronic {
...
void notify(std::atomic<T>& atom, T val) noexcept;
void expect(std::atomic<T> const& atom, T val) const;
};

expectでatomの値がvalになるまでブロックする。notifyでatomの値をvalに変える。

P0127R0: Declaring non-type template arguments with auto

型をテンプレート実引数で取る非型テンプレート実引数の宣言のための文法の提案。

テンプレートは非型をテンプレート実引数に取れる。

template < int N >
struct S { int value = N ; } ;

S<123> s;

非型テンプレート仮引数の型をテンプレート化したいことはよくある。

template < typename T, T v >
struct S { T value = v ; } ;

S< int, 123 > s ;

この時、わざわざ型を指定するのが面倒だ。コンパイラーは式を評価した結果の型がわかるのだから、実引数から型推定してほしい。そこで、autoキーワードを使った文法で、この型推定を行わせる機能を提案している。

template < auto v >
struct S { decltype(v) value = v ; } ;

S<123> s ;

P0128R0: constexpr_if

static_ifに変わるコンパイル時条件分岐、constexpr_ifの提案。


constexpr_if( condition )
{
// conditionがtrueの時に評価されるブロック
}

constexpr_if( condition )
{
// conditionがtrueの時に評価されるブロック
}
constexpr_else
{
// conditionがfalseの時に評価されるブロック
}

static_ifは反対多数により否決されたが、やはりほしいとのことで復活した。static_ifに対して上げられた反対意見を解決するべく、制限が多い。

  • ブロックスコープの中でのみ使える
  • 新しいスコープを導入する
  • 条件式には、どちらのブランチもwell-formedになる値が存在する

利用例には様々なものが考えられるが、簡単なコンパイル時条件分岐のために、関数テンプレートのオーバーロードを書くのがダルい場合に使える。

template < typename T >
void f( T && t )
{
// tを処理
}

template < typename T, typename ... Rest >
void f( T && t, Rest ... rest )
{
// tを処理

// 残りを処理
f( std::forward<Rest>(rest)... ) ;
}

constexpr_ifを使えば、以下のように書ける。

template < typename T, typename ... Rest >
void f( T && t, Rest ... rest )
{
// tを処理

// パラメーターパックRestにまだ引数があるのならば残りを処理
constexpr_if( sizeof...(Rest) != 0 )
{
f( std::forward<Rest>( rest ) ... ) ;
}
}

この提案の実験的な実装例は以下にある。

faisalv/clang at static_if

P0129R0: We cannot (realistically) get rid of throwing moves

variantに渡す型のムーブに無例外保証がないと、variantはempty/invalidな状態になりうるので、ムーブは例外を投げない制約を設けようという意見に対し、ムーブに無例外制約を化すのは現実的に無理だとする反論。

そもそも、「例外を投げるムーブ」とはなにか。

コンストラクターの場合、単にムーブコンストラクターのみではなく、以下の式に当てはまるものすべてになる。

X(move(rhs))

もし、オーバーロード解決がコピーコンストラクターを選択したら、当然コピーコンストラクターが対象になる。

同様に、代入演算子の場合、以下の式に当てはまるものすべてになる。

lhs = move(rhs)

オーバーロード解決がコピー代入演算子を選択したら、コピー代入演算子が対象になる。

では、そのようなコードはどのくらい一般的なのか。

struct X
{
    X(const X&);
};

このようなクラスは、デフォルトのムーブコンストラクターの生成が抑制されるので、ムーブは実際にはコピーになる。

では例外を投げるムーブは禁止できるのか?

  • コア言語で? 超互換性がないC++のforkをつくりたいのであれば、できるんじゃね?
  • 標準ライブラリで? かなりの互換性がないC++のforkを作りたいのであれば、できるんじゃね?
  • std::variantで? まあできるだろうけど、現実にかかれている結構なコードの型がvariantで使えなくなるぞ。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-12-14

自由ソフトウェア財団の認定する自由なコンピューター:Libreboot T400

Libreboot T400 laptop now FSF-certified to respect your freedom — Free Software Foundation — working together for free software

Libreboot T400 | Ministry of Freedom

自由ソフトウェア財団も認定する自由なコンピューターとして、Minifree(Ministy of Freedom、自由省)からLibreboot T400が発表された。

これは、Lenovo T400の中古品で、ファームウェアが完全に自由なBIOS実装であるLibrebootに変更されている。悪名高いバックドア実装用実行環境であるIntel Management Engineを無効にしても動く旧世代のCPUを使っている。ブートローダーには自由なブートローダー実装であるGrub 2が使われている。OSはUbuntuから不自由パッケージを取り除いた自由なGNU/LinuxディストロのTrisquelを使っている。

価格は、メモリ8GBでSSD240GBのものが678ユーロ(9万円)だ。

ところで、しばらく前にKickstarterで話題になったPurismだが、この機会に調べてみると、どうやら宣伝と違い、全然自由ではないようだ。

まず、CPUとチップセットが、Corebootで現在完全に自由な実装に置き換えているものではない。さらに、バックドア実装用のIntel Management Engineが無効にできる旧世代のCPUではない。さらに、購入者の話によると、出荷時のBIOSはCorebootではなくプロプライエタリな商用BIOSだったという。

自由なBIOS実装といえば、Corebootが有名だったが、Corebootは現在、プロプライエタリなバイナリブロブに汚染されてしまっている。

かわりに、Librebootを使うべきだ。こちらには自由な実装しか含まれていない。

librebootのFAQには興味深い情報がたくさんある。

2015-12-11

C++標準化委員会の文書のレビュー: P0110R0-P0119R0

P0110: Implementing the strong guarantee for variant<> assignment

強いvariantの実装の考察。

型Aのオブジェクトを保持するvariantに型Bのオブジェクトを代入しようとした時、型Aのオブジェクトを破棄してから型Bを構築するわけだが、もし、型Bのオブジェクトの構築時に例外が投げられた場合、型Bは構築できていないし、型Aは破棄したあとで、variantはどの型も保持していない無効な状態、あるいは空の状態になる。

variantはこのような無効な状態を作り出さない強い保証を持つべきだとの意見が多い。では、そのような強いvariantの実装はどのようになるのか。

型Bが無例外保証のあるコンストラクターを持つ場合、型Aのオブジェクトを破棄して、型Bのオブジェクトを構築すればよい。構築は絶対に失敗しない。

型Bが無例外保証のあるムーブ構築可能な型であるばあい、つまりstd::nothrow_move_constructible<B>::valueがtrueである場合、型Bのオブジェクトをスタックに構築して、型Aのオブジェクトを破棄して、型Bのオブジェクトをvariantのデータメンバーにムーブ構築して、スタック上の型Bのオブジェクトを破棄する。

型Bに無例外保証がないが、型Aにはある場合、型Aのオブジェクトをスタック上にムーブ構築して退避させ、型Bのオブジェクトを構築すればよい。

型AにもBにも無例外保証がない場合、バッファー上に型Bのオブジェクトを構築して、型Aのオブジェクトを破棄して、バッファー上の型Bをvariantの新しいオブジェクトとする。一般に、variantに2つ以上の無例外保証のない型がある場合、この実装になる。

問題は、そのバッファーはどこに取るべきかということだ。variantのデータメンバーに取ると、variantのサイズが2倍になってしまう。動的確保は好ましくない。すると、一般的にダブルバッファリングと呼ばれている、variantのサイズを2倍にする方法しかない。

emplaceについては、その存在意義から、ダブルバッファリングはせずに、基本的な例外保証のみに止めたほうがよいとしている。

論文著者による強いvariantの実装例が公開されている。

https://bitbucket.org/anthonyw/variant

P0112R0: Networking Library (Revision 6)

Boost.Asioベースのネットワークライブラリの提案。

P0113R0: Executors and Asynchronous Operations, Revision 2

実行媒体をポリシーベースで選択できるexecutorと非同期実行ライブラリの提案

以下のように使う。

// 非同期実行
post( p[] {
    // 処理
} ) ;

// スレッドプール内で非同期実行

thread_pool pool ;

post( pool, []{
    // 処理
} ) ;

asyncのようにfutureを受け取って結果を待つこともできる。

std::future<int> f =
    post( use_future( []{
        return 32 ;
        } ) ;

int result = f.get() ;

似たような提案が乱立していて、executorという用語も別々の意味で使っているので、混同しやすい。

P0114R0: Resumable Expressions (revision 1)

resumable式の提案。

resumable void f( int n )
{
    int i = 1 ;
    while( true )
    {
        std::cout << i << std::endl ;
        ++i ;
        if ( i > n )
            break ;

        break resumeale ;
    }
}

int main()
{
    resumable auto r = f( 5 ) ;

    while ( !r.ready() )
    {
        r.resume() ;
    }
}

コルーチンとresumable関数が分かれていた時の文章も混じっていてわかりにくい。

P0116R0: Boolean conversion for Standard Library types

std::bitset, std::chrono::duration, std::complexにexplicit operator boolを追加する提案。

bitsetは(count != 0)を返す、つまりすべてのビットが立っていない時にfalseを返す。

durationは( *this == zero() )を返す。つまり、ゼロのときにfalseを返す(durationのゼロ表現は結構面倒)

complexは、(real() != T() || imag() != T())を返す。つまり、real()とimag()がともにfalseの場合にfalseを返す(ド・モルガンの法則)

この文書で提案しているのはこの3つだけだが、他のライブラリにもexplicit operator boolを提供する考察もしている。例えば、weak_ptrは妥当なポインターを参照しているかどうか、threadは呼び出し元と同じidかどうか。futureはvalid()など。

weak_ptrは保持しているポインターの値と混同しそうだし、threadの挙動はちょっとわかりにくい気がする。

P0117R0: Generic to_string/to_wstring functions

以下のようなto_string/to_wstringを追加する提案。

template<class Ch, class Tr = char_traits<Ch>, class Alloc = allocator<Ch>, class... Args>
basic_string<Ch,Tr,Alloc> to_basic_string(Args&&... args)
{
    basic_ostringstream<Ch,Tr,Alloc> stream;
    stream.exceptions(ios_base::failbit | ios_base::badbit);
    // 文書はbinary foldの文法を間違えているので修正
    ( stream << ... << forward<Args>(args) );
    return stream.str();
}

template<class... Args>
string to_string(Args&&... args)
{
    return to_basic_string<char>(forward<Args>(args)...);
}

template<class... Args>
wstring to_wstring(Args&&... args)
{
    return to_basic_string<wchar_t>(forward<Args>(args)...);
}

リファレンス実装が文書に乗る程度の大きさ。C++17で入る予定のfold式を使っている。しかも文法を間違えている。

使い方


auto s123 = to_string( 123 ) ;
auto s123456 = to+string( 123, 456 ) ; 

マニピュレーターも使える。

[PDF] P0118R0: Concepts-TS editors report

Concept TSの編集者の報告書。変更点が記されている。

[最高に余白を無駄遣いしているPDF] P0119R0: Overload sets as function arguments

極めて読みづらい冒涜的に失礼なレイアウトだが、内容は面白い。

値を引数にとって処理をして返す関数fのオーバーロード群があるとする。

int f( int ) ;
double f( double ) ;
// ...その他多数

さて、std::transformで、値に関数fを適用したいとする。以下のように書くと動かない。


template < typename Iter >
void g( Iter first, Iter last )
{
    // エラー
    // fはオーバーロード関数群
    std::transform( first, last, f ) ;
}

fというのは単一の関数名ではなく、オーバーロードされた関数群だからだ。

これを解決するには、オーバーロード解決後の関数ポインターを得るように、明示的にキャストしてやる必要がある。


using value_type = typename std::iterator_traits::value_type ;
std::transform( first, last,
    static_cast< typename value_type (*)( value_type ) >(&f) ) ;

これには、オーバーロードされている関数群fのシグネチャを正確に把握する必要がある。例えば、fのシグネチャは以下のようになっているかもしれない。

int f( int, int dummy = 0 ) ;
double f( double ) ;

こうなると、もはや明示的なキャストを使って汎用的なコードを書くことはできない。

lambda式を使えば、汎用的なコードを書くことができる。

std::transform( first, last,
    []( auto && e ) { return f( std::forward<decltype(e)>(e) ) ; }
) ;

しかし、やはり最初の例のように書きたい。

std::transform( first, last, f ) ;

そこで、このような記述を可能にする提案。

具体的には、テンプレートの実引数推定で、実引数fがオーバーロード関数群であるid-expressionであった場合は、以下のようなlambda式の転送関数を暗黙に生成するようになる。

[]( auto && ... args )
{
    return f( std::forward< decltype(args)>(args)... ) ;
}

名前がオーバーロード関数群ではない場合、このlambda式は生成されないので、既存のコードとの互換性の問題もない。


template < typename T > void f( T ) ;

// オーバーロード関数群
void g( int ) ;
void g( double ) ;

// オーバーロード関数群ではない
void h( int ) ;

int main()
{
    f( g ) ; // lambda式が生成される
    f( h ) ; // void (*)( int )型の関数ポインターが渡される
}

また、演算子を直接渡すこともできる。

std::sort( begin(v), end(v), operator < ) ;

テンプレートの実引数推定に手を入れているので、思わぬ恩恵もある。例えば、オーバーロード関数名の変数への代入には、実は実引数推定が働くので、オーバーロード関数やオーバーロード演算子からラムダ転送式を生成して、クロージャーオブジェクトを得ることができる。

int main()
{
    auto lc = operator < ;

    lc( 1, 2 ) ; // 1 < 2
}

これは、以下のようなコードと同等だ。

int main()
{
    auto lc = []( auto && l, auto && r )
        {
            return std::forward<decltype(l)>(l) < std::forward<decltype(r)>(r) ;
        } ;

    lc( 1, 2 ) ;
}

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-12-08

C++標準化委員会の文書のレビュー: P0100R0-P0109R0

P0100R0: Comparison in C++

C++に様々な比較用のカスタマイズ可能な共通ライブラリを追加する提案。

一口に比較といっても、patial order, weak order, total order、そしてequivalenceがある。アルゴリズムやデータ構造によって、要求する比較の強さや、提供できる比較の強さは異なる。従来のC++では、このような異なる強さの比較が存在するにもかかわらず、すべての比較のcustomization pointは、単一の演算子のオーバーロードで提供されてきた。

この提案では、以下のような関数をstd名前空間に追加し、必要によってライブラリがオーバーロードして上書きすることによって、customization pointを提供するものである。

template<typename T> bool partial_less(const T&,const T&);
template<typename T> bool weak_less(const T&,const T&);
template<typename T> bool total_less(const T&,const T&);

また、同値比較としては以下の関数が追加される。

template<typename T> bool partial_unordered(const T&,const T&);
template<typename T> bool weak_equivalence(const T&,const T&);
template<typename T> bool total_equal(const T&,const T&);

P0101R0: An Outline of a C++ Numbers Technical Specification

SG6による数値まわりの提案をまとめた文書。

C++ Parametric Number Type Aliases

メタプログラミングにより数値計算をして、コンパイル時に値を保持するのに必要な数値型のビット数を求めることができる。しかし問題は、その求めたビット数から整数型を選択するライブラリが提供されていない。そこで、この提案では、そのようなライブラリを提案する。

exact_2uint<32> a; // きっちり32bitの符号なし組み込み整数型
least_2int<19> a; // 少なくとも19bitを持つ符号付き整数型
fast_2int<7> a; // 少なくとも7bitを持つ高速な符号付き整数型

この提案で興味深いのは、符号付き整数型は、負数を2の補数で表現すると厳密に規定されていることだ。

浮動小数点数型は、以下の通り、

exact_2ieeefloat<64> a; // きっちり64bitの浮動小数点数
fast_2ieeefloat<32> a; // 高速な少なくとも32bitの浮動小数点数
least_2ieeefloat<80> a; // 少なくとも80bitの浮動小数点数

浮動小数点数はIEE 754で規定されている浮動小数点数表現を使うと厳密に規定されている。

そして、符号付き整数型、符号なし整数型、浮動小数点数型の最大のビット数を得るためのプリプロセッサーマクロ、MAX_BITS_2INT, MAX_BITS_2UINT, MAX_BITS_2IEEEFLOATも追加される。プリプロセッサーマクロである理由は、コンパイル時に分岐できるようにするためだ。

P0103R0: Overflow-Detecting and Double-Wide Arithmetic Operations

オーバーフローを検出できる整数演算ライブラリと、演算結果を2倍のサイズの整数型で返してくれるライブラリと、演算結果を同サイズの整数型2つに分けて返してくれるライブラリの提案。

結論から言うと、クソコード生成ライブラリ。

例えば、2つの符号付き整数を加算する場合は以下のようになる。

int a , b 
std::cin >> a >> b ;

int sum{} ;

bool b = std::overflow_add( &sum, a, b ) ;

if ( b )
    std::cout << "overflow detected." << std::endl ;
else
    std::cout << "result: " << sum << std::endl ;

overflow_xxxは、第一引数に結果を格納する整数型へのポインターを取り、第二引数以降に演算する整数の値を取る。演算結果がオーバーフローした場合、trueが返され、ポインターには書き込まれない。演算結果がオーバーフローしなかった場合、falseが返され、演算結果がポインターを関節参照して書き込まれる。

提案は他にも、倍精度演算を提供している。結果は、演算に使った型の倍のサイズの整数型でうけとるか、2つの整数型で受け取ることができる。型は、<cstdint>から持ってくるか、P0102で提案されている型を使うなどする。この提案では整数型は規定しない。

std::int32_t a , b ;

std::cin >> a >> b ;
std::int64_t sum = std::wide_add( a, b ) ;

2つの整数型で受け取る場合、上位ビット列を戻り値で返し、下位ビット列を第一引数のポインターを介した間接参照で書き込んで返す。下位ビット列は必ず符号なし整数型になる。

std::int32_t a, b ;
std::cin >> a >> b ;

std::uint32_t lower ;
std::int32_t upper = std::split_add( &lower, a, b ) ;

ちなみに、論文著者はLawrence Crowlで、GCCが提供しているオーバーフロー検知機能付きの演算ライブラリを引数の順序などを少し手直しした他はそのまま持ち込んできた様子だ。きわめて原始的でわかりにくい設計をしていて、これを使ったコードはLinusも激怒するほどのクソになる。

本の虫: Linus Torvals、クソコードにブチギレ

オーバーフロー検知機能付きの演算はほしいが、ライブラリで提供するのは無理がある。思うに、コア言語でのサポートが必要だろう。例えば以下のような

try {
    int sum = overflow_cast<int>( a + b ) ;
} catch( std::overflow_exception e )
{
    // オーバーフローが発生した
}

P0104R0: Multi-Word Integer Operations and Types

任意のワード長での整数演算ができるmulti_int/multi_uintの提案。

template<int words> multi_int;
template<int words> multi_uint;

実装が提供する最大の組み込み整数型ですら演算精度が足りない場合、複数の整数型のオブジェクトを組み合わせて、多倍長精度演算を行う必要がある。そのような演算を手で書くのは難しい。標準で提供されているべきである。

multi_int/multi_uintは、組み込み型が提供している演算子を同じ意味でオーバーロードしている。また、通常の整数と同じように、暗黙にboolに変換できるようboolへの変換関数もある。

提案では、このような演算を整数型の配列に対して行う低級な関数も提供している。これによりユーザーは自前の多倍長演算も実装できる。提案では関数のシグネチャに(が足りずに文法が間違っていることと、divがないように見える。

無限精度整数ライブラリも標準にほしいが、こちらもほしい。

P0105R0: Rounding and Overflow in C++

数値演算に対する丸め方法とオーバーフロー時の挙動を指定できる機能。

浮動小数点数の丸め方法はすでに、numeric_limitsのround_styleで取得したり、fenv.hで設定できる。しかし、既存の方法ではある重要な場合における挙動がかけている。

もし、値が表現可能な2つの値から等しく離れていた場合、どちらの方向に丸められるのかということだ。

論文では、これに対して、以下の方向を考察している。

負の無限大方向 正の無限大方向
ゼロ方向 ゼロから離れる方向
偶数方向 奇数方向
実行時間最速 生成コード最小
どうでもいい

この考察を元に、提案では以下のenumがある。

enum class rounding {
  all_to_neg_inf, all_to_pos_inf,
  all_to_zero, all_away_zero,
  all_to_even, all_to_odd,
  all_fastest, all_smallest,
  all_unspecified,
  tie_to_neg_inf, tie_to_pos_inf,
  tie_to_zero, tie_away_zero,
  tie_to_even, tie_to_odd,
  tie_fastest, tie_smallest,
  tie_unspecified
};

このうち、all_away_zero, all_to_even, all_to_odd, tie_to_neg_inf, tie_to_pos_inf, and tie_to_zeroは、conditionally supportedとなっている。規格準拠の実装はサポートする義務がない。

このモードは、T round(mode,U)という関数に渡すことで使用する。

この提案はオーバーフローが起きたときの挙動を指定できるT overflow( mode, T upper, U value )も提案している。

モードは以下のようになっている。

enum class overflow {
  impossible, undefined, abort, exception,
  special,
  saturate, modulo_shifted
};

impossible: 完全に正しいプログラムでありオーバーフローは数学的に決して起こらない。コンパイラーへのヒントを与えるのにも使える

undefined: オーバーフローは極めてまれにしか起こらない。オーバーフローが起こった時の挙動は未定義

abort: オーバーフローが起こったらabortする。

quick_exit: オーバーフローが起こったらquick_exitを呼び出す

exception: オーバーフローが起こったら例外を投げる

special: オーバーフローが起こったことをしめす特殊な値を返す(IEEE 754の規定する浮動小数点数などに存在する)

saturate: 有効な値の範囲でもっとも近い値を返す

modulo_shifted: 0からzまでの範囲の場合、結果はx mod (z+1)となる。2の補数表現による挙動

こちらの設計のほうがまだ比較的読みやすい。

また、提案では丸め方法とオーバーフロー挙動を同時に指定できる関数も提案している。

P0106R0: C++ Binary Fixed-Point Arithmetic

2進数固定小数点数ライブラリの提案。

提案を読んだがどうにも使い方が難しい設計と命名だ。筆者に数学的素養がないためかもしれないが、もう少し誰でも簡単に使える設計になっていた方がいいのではないか。

論文は2進数固定小数点を標準でサポートしている既存の言語としてAda, COBOL, CORAL 66, JOVIAL, PL/1を挙げている。CORALとJOVIALは初めて聞いたプログラミング言語だ。

[PDF] P0197R0: Better support for constexpr in std::array

constexprが暗黙にcosntを意味しなくなったので、std::arrayがconstexpr対応ではなくなってしまったのを修正する提案。

P0108R0: Skeleton Proposal for Thread-Local Storage (TLS)

SIMDやGPGPUなどの軽量実行媒体では、TLSはうまく動かない。これは既存のライブラリが動作しない問題を引き起こす。

最も問題になるのは、math.hだ。多くのmath関数は、エラー通知にerrnoを使う。math_errhandling & MATH_ERRNOが非ゼロである場合、errnoに具体的なエラー内容が書き込まれる。errnoは、互換性のために必要だが、その特性からTLSが使われている。

SIMDやGPGPUによる並列化が行われるコードは、特に数学関数を使いたいはずだ。しかし、数学関数はerrnoに書き込むために競合を起こして使えない。

この問題はどうにかして解決しなければならない。軽量実行媒体でもTLSを使えるようにする案もあるが、ここでは、数学関数側で対処する案が考察されている。

1. 制約を加える

軽量実行媒体の文脈では、数学関数はmath_errhandlingやerrnoにアクセスしない。

2. errnoを関数の引数を経由して受け取る

errnoにアクセスするすべての数学関数に、例えば以下のようにオーバーロードを追加しなければならない。

double acos(double x, int *errnm);

3. errnoを戻り値で返す。

tupleなどを使わなければならない。

std::tuple< double, errno_t > acos( double x ) ;

そもそも、errnoでエラー処理をしているプログラマーは少ないと思うので、1.が最も妥当な案だと思う。本当にエラー処理をしたければ実装依存の方法を使うのではないか。

[PDF] P0109R0: Function Aliases + Extended Inheritance = Opaque Typedefs

3つもの大きめの変更が提案されているので、提案を分割してほしい気がする。

この文書では、Opaque Aliasを提案している。

1. Opaque Alias

かつて、強いtypedefとも呼ばれていた機能。以下のようなエイリアス宣言に似た文法を持つ。

using identifier = access-specifier type-id opaque-definition

access-specifierは、お馴染みのpublic/protected/privateだ。それぞれ、以下のような意味を持つ。

  • private: 暗黙の型変換を認めない
  • public: is-a関係。暗黙の型変換を認める
  • protected: is-implemented-as関係。opaque型の定義の中でのみ暗黙の型変換を認める

以下が利用例だ。

// intのopaque alias
using type = public int ;

type x = 0 ; // OK 暗黙の型変換

// OK、ただしyの型はint
auto y = x + x ;

publicのopaque aliasは、あらゆる箇所で内部型との暗黙の型変換を許可する。そのため、int型で初期化できる。"x+x"の結果の型がintになるのは驚くかもしれないが、仕組みを知れば当然だ。まず、operator +(type, type)は宣言されていないので、typeがintに暗黙に型変換されたうえで、組み込みのint opeartor +(int,int)が呼ばれる。そのため、結果はint型になる。

privateを使えば、一切の暗黙の型変換が禁止される

using type = private int ;

type x1 = 0 ; // エラー、暗黙の型変換は禁止

type x2 = reinterpret_cast<int>(0) ; // OK
type x3{ 0 } ; // OK

auto y = x2 + x2 ; // エラー

operator =やoperator +のような基本的な操作で、完全に暗黙の型変換を許すか、全面的に許さないかだけでは使いづらい。また、式を評価した結果の型についても、柔軟に決めたい。そこで、opaque aliasには、opaque-definitionを記述することができる。

using type = public int
{
    type opeartor + ( type x, type y )
    {
        return type { int{x} + int{y} } ;
    }
} ;

type x{ 0 } ;

// yの型はtype
auto y = x + x ;

// zの型はint
// 暗黙の型変換でint operator - ( int, int )が呼ばれる
auto z = x - x 

opaque定義では、このようにトランポリン関数を定義できる。こうすることによって、任意の演算子の挙動を変えられる。operator -は定義していないので、暗黙の型変換により組み込みのint operator -(int,int)が呼ばれる。

目的によっては、operator -を使う事自体が想定できない場合がある。そのために、deleted定義が使える。また、引数の型を変更するだけのトランポリン関数の場合、default化することもできる。

using type = public int
{
    type operator +( type, type ) = default ;
    type operator -( type, type ) = delete ;
} ;

type x{ 0 } ;

// yの型はtype
auto y = x + x ; 

// エラー、deleted定義
auto z = x - x ; 

// OK
// 暗黙の型変換でint operator * (int, int)が呼ばれる
auto w = x * x ;

あらゆる演算子に対してdeleted定義を書くのは面倒だ。そこで、アクセス指定子にprotectedを指定すると、opaque定義以外の暗黙の型変換をしないようになる。


using type = protected int
{
    type opeartor +( type, type ) = default ;
} ;

type x1 = 0 ; // エラー、暗黙の型変換は使えない
type x2{ 0 } ; // OK


auto y = x - x ; // エラー

ちなみに、opaque aliasのopaque aliasも作れる。

using A = private int ;
using B = public A ;

A a{} ;
B b{} ;

// OK
// BはAからのpublicなopaque alisなので、
// BからAに暗黙に型変換できる
a = b ;

// エラー
// Bにoperator =( A )は定義されていない
// Aは暗黙の型変換を許さない
b = a ;

opaque aliasをクラス型に対して適用する場合、メンバー関数に対しては、自動的にトランポリン関数が作られる。

using type = private std::vector<int> ;

type t ;

t.push_back( 0 ) ;

// 元のクラス型をtype型にしたトランポリン関数を呼び出す。
// トランポリン関数は元のメンバー関数を呼び出し結果を返す
type y( t ) ;

opaque aliasはテンプレート宣言できる。

template < typename T >
using vec = private std::vector<T> ;

template  <typename T >
using INT = protected int
{
    INT operator +( INT, INT ) = default ;
    INT operator +( INT, T ) = default ;
    INT operator +( T, INT ) = default ;
}

エイリアス型と元の型は、is_sameやis_base_ofなどの型の関連性を調べるtraitsでは、異なる型で派生関係にもないと判断される。typeidも別の型になる。is_integralなどの単一の型を調べるtraitsでは、エイリアス型は元の型と同じになる。

sizeofの結果は元の型と同じで、メモリレイアウトなどは全て同じ。opaque aliasを使うことによる実行時コストは発生せず、reinterpret_castを使って元の型にキャストしても動作する。

また、P0109は、関数エイリアスを提案している。

関数に別名を付けたい需要は昔からある。例えば、<algorithm>はオブジェクトの比較に、operator *lt;を使う版と、比較関数を受け取る版の関数が存在する。それぞれ、やっていることは同じなのにコードが異なる。

template < typename T >
constexpr const T & min ( const T & a, const T & b )
{
    return a < b ? a : b ;
}

template < typename T, typename Compare >
constexpr const T & min( const T & a, const T & b, Compare comp ) ;
{
    return comp( a, b ) ? a : b ;
}

何らかの方法で、compに関数エイリアスとしてoperator <を割り当てることができれば、同じように自然に書ける。

template < typename T, typename Compare >
constexpr const T & min( const T & a, const T & b, Compare comp ) ;
{
    using operator <() = comp ;
    return a < b ? a : b ;
}

また、P0109は、派生に対する拡張も提案している。配列とCV修飾されている型を除く、基本型から派生できるようにする拡張だ。

// OK
struct X : int ;

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-12-04

江添ボドゲ会@12月開催のお知らせ

江添ボドゲ会@12月 - connpass

12月のボードゲーム会は23日に開催します。

詳細はリンク先のconnpassで。

場所

江東区塩浜2-14-2 アライハウスⅡ 101号室

最寄り駅は東西線木場駅。都営バスならば業10か錦13が塩浜二丁目に止まります。

木場駅4b出口から出て南下。イトーヨーカドーを通り過ぎ、中央自動車学校と歩道橋がある塩浜通りで東に進み、サンクスとすき家に挟まれた道を南下し、木場リサイクルの向かい側のマンションの一階の101号室。

入り口のインターホンで101号室を呼び出してください。

2015-12-01

C++標準化委員会の文書のレビュー: P0090R0-P0099R0

P0090R0: Removing result_type, etc.

result_type, argument_type, first_argument_type, second_argument_typeというネストされた型名の廃止。

これらのネストされた型名はdecltype以前の機能であり、もはやその役割は別の機能(result_ofなどのtraits)で担うことができる。また、labmda式のクロージャーオブジェクトなどresult_typeを持たない型が多数あり、もはやresult_typeなどを使うコードは、ジェネリックではない。

ただし、<random>にあるresult_typeなどは意味があるため、残す。

not1, not2の廃止。これらはVariadic Templates以前の機能であり、いまはより優れたnot_fnで代替できる。

これらの廃止した項目を、規格の中で互換機能を記述する箇所である、Annex C: Compatibilityに移動。

互換性のために、これらの除去した機能を実装するコンパイラーはC++17規格準拠であるという例外的な文面を追加する。

P0091R0: Template parameter deduction for constructors (Rev. 2)

関数テンプレートにある実引数推定(argument deduction)をクラステンプレートのコンストラクターにも提供する提案。

関数テンプレートには、テンプレート実引数を関数の実引数から推定してくれる機能がある。

template < typename T >
void f( T t ) ;

int main()
{
    f( 0 ) ; // f<int>
    f( 0.0 ) ; // f<double>
}

しかし、クラステンプレートにはそのような機能はないため、テンプレート実引数を手書きしなければならない。

template < typename T >
struct X
{
    X( T t ) ;
} ;

int main()
{
    X x(0) ; // エラー
    X<int> x(0) ; // OK
}

この問題に対する伝統的な解決方法は、関数テンプレートに実引数推定させてクラステンプレートを返す方法がある。

template < typename T >
X<T> make_x( T && t )
{
    return X<T>( std::forward<T>(t) ) ;
}

int main()
{
    auto x = make_x( 0 ) ; // X<int>
}

この解決方法の問題は、まずmake_x関数テンプレートを書かなければならないということだ。ライブラリごとにそのようなmake関数のインターフェースは異なるので、ライブラリごとにmake関数を使う際にドキュメントを参照しなければならない。コピーもムーブもできない型に対してはmake関数を提供できない。

この提案による機能は2つある。まずは、クラステンプレートのコンストラクターが型を推定する機能

pair p(1, 1.0 ) ; // pair<int, double>
tuple t( 1, 1.0, "hello" ) ; // tuple<int, double, const char * >

もうひとつは、コンストラクターがクラステンプレートのテンプレート実引数に依存していない場合でも、実引数推定を行えるようにする機能。

例えば、vectorのイテレーターを取るコンストラクターは、上記の方法では実引数推定できない。

template < typename T, typename Allocator = std::allocator<T> >
class vector
{
    template < typename InputIterator >
    vector( InputIterator first, InputIterator last ) ;
} ;

このため、コンストラクターからクラステンプレートのテンプレート仮引数に依存を発生させ、実引数推定をさせるためのマッピング機能が提案されている。

提案されているマッピングの文法案

template<typename T, typename Alloc = std::allocator<T>> struct vector {
  /* ... */
};
template<typename InputIterator>
vector(InputIterator first, InputIterator last)
-> vector<typename iterator_traits<InputIterator>::value_type>

極めて読みづらい文法だ。これはライブラリ作者が書くものであって、単なるライブラリのユーザーは存在を気にする必要はないのが救いか。

P0092R0: Polishing chrono

<chrono>を改良する提案

time_pointにインクリメント演算子とデクリメント演算子のオーバーロードを追加

durationとtime_pointに新しい丸めモードを追加、floor(負の無限大方向に丸める)、ceil(正の無限大方向に丸める)、round(近い方に丸める)

符号付きdurationにabsを提供

P0093R0: Simply a Strong Variant

ダブルバッファリングを含む対策をすることでnever empty保証のある強いvariantの提案。

never empty保証をするためには、ダブルバッファリングが必要になる。これはvariantのサイズを2倍にしてしまう。

論文は、プログラマーはコストを犠牲にしてもプログラムの正確性を重視すると主張している。また、無例外保証のあるムーブを提供している型だけ渡されたならば、メタプログラミングによりダブルバッファリングを回避する実装が可能であること、Transactional Memoryの発展によって、将来はあらゆる型でダブルバッファリングをしなくてもすむようになる(かなり疑わしいが)ので、将来の拡張性があることなどを挙げている。

P0094R0: Simply a Basic Variant

P0093の省略版のような文書。N0093とほぼ同じ。

P0095R0: The Case for a Language Based Variant

コア言語でvariantに対応する提案。

純粋にライブラリベースのvariant実装は使いづらい。一般ユーザーにすらSFINAEによるコンパイル時条件分岐を強いる。

そこで、コア言語による対応が必要だとしている。

提案では、以下のようにvariantを宣言できる。

enum union param
{
    int age ;
    std::string name ;
    std::string address ;
} ;

コア言語で対応するので、switch文もvariantに対応できる。

struct Person
{
    int age ;
    std::string name ;
    std::string address ;

    void set( param p )
    {
        switch( p )
        {
            case age a :
                age_ = a ;
                break ;
            case name n :
                name = n ;
                break ;
            case address a :
                address = a ;
                break ;
        }
    }
} ;

int main()
{
    Person p ;
    p.set( param::name("Ada") ) ;
}

やはりコア言語で対応すると使いやすい。

P0096R0: Feature-testing recommendations for C++

プリプロセッサーマクロによる機能テストのためのライブラリ。C++17機能に対応する機能テストが追加されている。

P0097R0: Use Cases for Thread-Local Storage

TLSの利用例を示した文書。

スレッドローカルストレージには長年の歴史があるが、近代的な並列実行において、利用価値はあるのかどうか疑問視されることがある。この文書は、TLSは現代でも利用価値があると解説している。

問題は、SIMDやGPGPUではTLSはサポートしづらい。しかし、TLSがサポートされないのでは有益なコードが書きにくい。

提案では、SIMDやGPGPUという実行媒体の特性を解説した上で、TLSを現実的にサポートするための案をいくつか出している。たとえば、TLSに複数の種類を作る案。SIMDやGPGPUで使うTLSは全TLSの一部なので、その一部だけを絞り込めれば実用的なパフォーマンスになる。あるいは、SIMDやGPGPUのTLSは、非トリビアルなコンストラクターが実行されない制約をつけるなど。

RCUの解説。

[PDF] P0099R0: A low-level API for stackful context switching

N4397の改訂版。

タイトル通り、stackful context switchのための低級APIの提案。

ドワンゴ広告

ドワンゴはプログラミング講師を募集しているそうだ。

【教育事業】プログラミング講座 講師候補(正社員)|募集職種一覧|採用情報|株式会社ドワンゴ

応募にあたっての課題が、ドワンゴ プログラミング講師候補採用試験問題に書いてある。

正解が「オブジェクト指向プログラミング」となる問題文を作成せよという課題が興味深い。「最大200文字以内」という条件はいいとして、「正答の直接的な記載を禁ずる」ことと、「プログラミング言語名の記載を禁ずる」という条件がある。

筆者の頭にとっさに浮かんだ、まったく試験にふさわしくない問題分は、「Alan Kayが研究していたプログラミングのパラダイム」とか、「UMLで記述されるプログラミングのパラダイム」である。Alan Kayといえばわかりやすいが、しかしこれは歴史問題のようだ。UMLは一般にプログラミング言語とは認識されていないので、当然条件を満たすはずだ。

それはそうと、C++のオブジェクト指向は、Simulaの影響を受けている。オブジェクト指向という言葉を使った有名人はAlan Kayで、Alan Kay自身もプログラミング言語を開発しているのだが、C++を始めとする真面目な実務向きのプログラミング言語におけるオブジェクト指向のサポートは、Simulaの影響が大きい。

Alan Kay自身のオブジェクト指向を具体化したプログラミング言語の実装、Smalltalkは、実務向けのプログラミングにはあまり影響を与えなかった。Objective-Cや、あまり知らないがひょっとしたらSwiftがその影響を受けているかもしれない。Smalltalkは、単なるプログラミング言語ではなく、smalltalkというシェルでもあり、エディタでもある、プログラミング環境であり、プログラミング言語単体で切り離せなかったため、実務からは遠くなってしまったのだと思う。

ところで、最近のC++は、というよりも最近の言語は、あまりオブジェクト指向対応を謳っていない気がする。これは、いまさらに構造化プログラミング対応言語を謳わないのと同じで、オブジェクト指向が十分に一般化したためではないかと思う。

また、オブジェクト指向のプログラミング言語での取り入れ方もだいぶ変わった。オブジェクト指向の黎明期は、やたらと派生と継承ベースの設計が流行ったが、複雑な派生関係をユーザー側に露出させてしまうと、ライブラリが使いづらくなる。

最近は汎用的なインターフェース(制約)によるライブラリ設計が多いようだ。コンセプトが議論されているのもそのためだ。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-27

C++30周年を記念してCFrontのバグ調査をしてみた

Finding Bugs In The First C++ Compiler - What does Bjarne Think!

CFrontの30週年を記念して、C++の設計者にして最初の実装者であるBjarne Stroustrupの実装したC++実装、CFrontに静的解析ツールをかけてバグを洗い出してみた記事が上がっている。

C++の最初のコンパイラーのバグの調査:Bjarneは何考えてたんだ!

C++は先月30周年を記念したので、PVS-Studio開発部署は自前の静的コード解析ツールを使って、最初のC++コンパイラーであるCFrontのバグを探してみようと思い立った。これは奇妙なお祝いの仕方のように思われるだろう、とくに、C++の創始者であるBjarne Stroustrupを問い詰めるわけだから。彼の返答も載っている。

C++の30年の歩み、Bjarne Stroustrup on the 30th anniversary of Cfront (the first C++ compiler)

Bjarne Stroustrupによって開発されたC++は、C with Classesと認知されていた[訳注: これは謝り、C++はC with Classesの経験を元に新たに設計された]。C++の30周年を祝って作成されたこの時系列表は、この言語が1979年に始まって、1985年の10月14日に、BjarneのThe C++ Programming Language初版本とともにリリースされたと書いている。

この時系列表によれば、1985年のCFrontの初リリースから、開発が終了する1993年までに、3度のリリースがあったようだ。

Cpreを元にしたCFrontは、完全なパーサーと、シンボルテーブルと、クラスや関数などのツリー構築を備えている。C++の不思議な制限の多くは、CFrontの実装上の制限に由来している。その理由は、CFrontはC++からCへの変換を行うからだ。つまり、CFrontとはC++プログラマーにとっての神聖なるアーティファクトなのだ。そこで、30周年を記念して、PVS-Studioを使って最初のバージョンをチェックしたくなった。

筆者はCFrontのコードを入手するために、Bjarne Stroustrupに連絡を取った。筆者はコードの入手だけでも長い話になるだろうと考えていたが、とても簡単だった。

ソースコードはオープンで、以下から誰でもダウンロードできる。

C++ Historical Sources Archive — Software Preservation Group

Bjarneによれば、CFrontを検証するのは難しいだろうとのことだ。曰く、

これはとても古いソフトウェアで、1MB 1Mhzのマシンで動作するよう設計されていて、オリジナルのPC(640KB)でも使われていた。これは一人の人間(私)の仕事の一部として開発されていたのだ。

そして、このプロジェクトを検証するのは不可能だと判明した。この当時、クラス名と関数名を分けるのに、ダブルコロン(::)ではなく単一のドット(.)を使っていたのだ。例えば、

inline Pptr type.addrof() { return new ptr(PTR,this,0); }

弊社のPVS-Studioアナライザーはこのコードを解析できない。そこで、筆者は同僚に、コードを見て、このような箇所を手で修正するように依頼した。一応動いたが、まだ問題はあった。アナライザーにコードをチェックさせてみると、混乱して解析を停止する箇所が多々ある。

とにかく、我々はこのプロジェクトを検証した。

結論から言うと、我々は重大な問題を発見できなかった。PVS-Studioが問題を発見できなかった理由は、

  • プロジェクトサイズが小さい。143ファイル、10万行のコードにすぎない
  • コードの品質が高い
  • PVS-Studioはコードの一部を認識できなかった

発見されたバグ

とはいえ、かのStroustrup本人のバグが発見されることを期待している読者をがっかりさせないためにも、コードをみてみよう。

コード1

typedef class classdef = Pclass;

#define PERM(p) p->permanent=1

Pexpr expr.typ(Ptable tbl)
{
    ...
    pclass cl ;
    ...
    cl = (Pclas) nn->tp;
    PERM(cl);
    if(cl == 0) error('i', "%k %s'sT missing", CLASS,s);
    ...
}

PVS-Studio warning: V595 The 'cl' pointer was utilized before it was verified against nullptr. Check lines: 927, 928. expr.c 927

clポインターはNULLに等しい可能性がある。if (cl == 0)によるチェックがその可能性を示唆している。問題は、このポインターはチェックの前に参照されている。問題はPERMマクロの中で発生している。

マクロを展開すると、

cl = (Pclass) nn-tp;
cl->permanent=1;
if (cl == 0) error('i', "%k %s'sT missing", CLASS,s);

コード例2

同じ問題。ポインターが参照された後にチェックされている。

Panme name.normalize(Pbaseb, Pblock bl, bit cast)
{
    ...
    Pname n;
    Pname nn;
    TOK stc = b->b_sto;
    bit tpdf = b->b_typedef;
    bit inli = b->b_inline;
    bit virt = b->b_virtual;
    Pfct f;
    Pname nx;
    if (b == 0) error('i',"#d->N.normalize(0)",this);
    ...
}

PVS-Studio warning: V595 The 'b' pointer was utilized before it was verified against nullptr. Check lines: 608, 615. norm.c 608

コード例3

int error(int i, loc* lc, char* s ...)
{
    ...
    if (in_error++)
        if (t!='t' || 4<in_error) {
            fprintf(stderr, "\nUPS!, error while handling error\n");
            ext(13);
        }
    else if (t == 't')
        t = 'i';
    ...
}

PVS-Studio warning: V563 It is possible that this 'else' branch must apply to the previous 'if' statement. error.c 164

筆者はこれに問題があるのかどうか判別できなかったが、コードのインデントが正しくはない。elseは直近のifに対応する。そのため、このコードは見た目通りに実行されない。

インデントを直すと、以下のようになる。

if (in_error++)
    if (t!='t' || 4<in_error) {
        fprintf(stderr, "\nUPS!, error while handling error\n");
        exit(13);
    } else if (t == 't')
    t = 'i';

コード例4

extern
genericerror(int n, char* s)
{
    fprintf(stderr,"%s\n",
        s?s:"error in generic library function",n);
    abort(111);
    return 0;
}

PVS-Studio warning: V576 Incorrect format. A different number of actual arguments is expected while calling 'fprintf' function. Expected: 3. Present: 4. generic.c 8

フォーマット指定が"%s"なので、文字列は表示されるが、変数nは使われない。

残念ながら(あるいは幸運にも)、筆者はこれ以上の問題を見つけられなかった。アナライザーは他にも警告を出していて、興味深いものもあるが、それほど深刻な問題ではない。

例えば、アナライザーは一部のグローバル変数名が気に入らない様子だ。

extern int Nspy, Nn, Nbt, Nt, Ne, Ns, Nstr, Nc, Nl;

PVS-Studio warning: V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'Nn' variable. cfront.h 50

他の例としては、ポインターの値をfprintf()関数で表示する際に、Cfrontは%iを使っている。現代版では、%pがある。とはいえ、筆者の識る限り、30年前に%pはなかったので、コードは正しい。

筆者はまた、この当時のthisポインターは別の方法で使われていたことに注目した。以下のような例だ。

expr.expr(TOK ba, Pexpr a, Pexpr b)
{
    register Pexpr p;

    if (this) goto ret:
    ...
    this=p;
    ...
}

inline toknode.~toknode()
{
    next = free_toks;
    free_toks = this ;
    this = 0;
}

見てわかるように、thisの値を変えるのは禁止されていなかった。今はthisポインターを変更するのは禁止されている。そのため、thisをnullと比較するのは現代では理屈に合わない。

筆者はまた、特に興味深いコード片を発見した。

/* this is the place for paranoia */
if (this == 0) error('i',"->Cdef.dcl(%d)",tbl);
if (base != CLASS) error('i', "Cdef.dcl(%d)",base);
if (cname == 0) error('i',"unNdC");
if (cname->tp != this) error('i',badCdef");
if (tbl == 0) error('i',"Cdef.dcl(%d,0)",cname);
if (tbl->base != TABLE) error('i',""Cdef.dcl(%n,tbl=%d)",
                              cname,tbl->base); 

Bjarne Stroustrupの返答

さて、Bjarneは30年前のエラーに対しどう返答するのか。以下が彼の返した返事だ。

  • CFrontはCpreからブートストラップされたが、完全な書きかえがあった。CFrontにはCpreのコードは一行もない
  • use-before-test-of-0はもちろんダメだが、興味深いことに、私が当時使っていたマシンとOS(DECと研究用UNIX)には、page zero write protectがあったので、このバグによる問題が実際発生したならば見逃されずにいただろう
  • if-then-elseバグは変だ。ソースを読んだところ、単なるインデント間違いではなく、実際の間違いであった。しかし、それほど問題ではない。違いは、異常終了するまえのエラーメッセージの些細な間違いでしかない。見つけられなかったのも無理はない。
  • そうだ。もっと読みやすい名前を使うべきだった。このコードを他の人間が長期間保守することは想定していなかった(それと私はタイプが下手だった)
  • そうだ。%pは当時なかった。
  • そうだ。thisの仕様は変更された。
  • そのパラノイアテストはコンパイラーのメインループに仕掛けてある。もしソフトウェアかハードウェアに不具合があったならば、そのテストのうちのどれかは失敗するだろう。少なくとも一度、そのコードはCFrontをビルドするツールのコード生成のバグを発見した。思うに、すべての大規模なプログラムは、「不可能」なエラーを発見するための「パラノイアテスト」を含むべきである。

結論

CFrontの価値は計り知れない。プログラミングの開発に多大な影響を与えたし、未だに開発され続けているC++言語を生み出した。C++を作って開発したBjarneの作業には感謝してもし尽くせない。この素晴らしいコンパイラーを検証出来なのはとてもよかった。

Andrey KarpovはProgram Verification Systemsの共同創始者かつCTOである。この会社の主な活動は静的コード解析ツール、PVS-Studioの開発である。AndreyはVisual C++エキスパートとして4回のMicrosoft MVP賞を獲得し、C++ Hintsウェブサイトを運営している。

パラノイアテストが興味深い。

例えば、Chromiumにも含まれている。

また、URLを忘れたが、どこかのオンラインゲームが、ゲームクライアントのメインループに絶対に失敗しないチェック(簡単な数値計算と結果の比較)を淹れてみたところ、少なからぬ割合のユーザーがチェックに失敗したという。

また、Microsoft Windowsのエラー報告で、xor eax,eaxのような絶対に失敗するはずのないコード箇所でクラッシュした報告を受け取った(ユーザーのCPUにそのようなエラッタはない)ので、調査してみたところ、そのユーザーのPCはショップで組み立て済みで販売されているもので、過剰にオーバークロックされていたとの話もある。

江添ボドゲ会@12月の日程調整

12月に江東区塩浜にある自宅でボドゲ会を開催したいので、日程調整のために調整さんを作ってみた。

12月の休日にボドゲをしたい人は調整をしてください。

ボドゲ会 | 調整さん

2015-11-26

カタン アメリカの開拓者たちの感想

「カタン アメリカの開拓者たち」を遊んでみたので、その感想を書いてみる。

アメリカカタンは、カタンと名前が付いているものの、全くの別ゲーだ。まず、ランダムタイルはなく、北アメリカの形をした固定ボードを使う。一部の資源タイルの数字だけ変動する。

ゲーム序盤は東側に変動する数字チップが載っているのだが、西側に都市を立てるにつれ、最も東側の数字チップが西に移動していく。これにより、ゲーム後半は東海岸が不毛の地と化す。ゴールドラッシュによる東から西への開拓移動を表現しているのだろう。

資源は、木、麦、鉄、石炭、牛である。羊ではなく牛なところがアメリカっぽい。

ゲームで建設できるものは:幌馬車、幌馬車を動かす、線路、列車、列車を動かす、発展カードである。幌馬車の建設には幌の部分に牛革が必要なのか、牛が必要である。実にアメリカっぽい。

カタンのように6面ダイスを2つ転がして、出た目の合計値と同じ資源タイルから資源が算出されるのは変わらない。何も取れない場合、金貨が1金手に入る。また、盗賊の代わりにアウトローがいる。実にアメリカっぽい。機能は盗賊と同じだ。7を振っても、資源を捨てる必要はない。

また、プレイヤーは複数の都市と物資を持っている。都市と物資は組になっていて、都市を設置すると物資が解放される。

ゲーム序盤は、幌馬車を建設し、幌馬車を移動させて、まだ都市を立てていない場所に移動させ、そして都市を設置する。都市の設置は建設ではなく、したがって建設コストはかからない。幌馬車をまだ都市が未建設の場所まで移動させるだけで良い。

都市を設置すると、物資が解放される。都市間と線路でつないで、列車を建設して、まだ物資が置かれていない他人の都市に移動させると、解放された物資をその都市に置くことができる。

一部の細かいルールは省略したが、これが、ゲームの基本的な流れだ。物資を一番早く運び終えたプレイヤーが勝利する。

さて、この「物資」なのだが、都市を建設した際に排出され、他人の都市に置くことで取り除くことができて、全部取り除くと勝利する。物資を置かれた都市には特に利益が発生しない。はたしてこの物資はなんなのだろうか。ゴミだろうか。ゴミに違いない。

こうして、私がアメリカカタンをプレイした時、我々は物資のことをゴミと読んでいた。「都市をさっさと立ててゴミを出したい」、「はやくゴミを運んで片付けたい」、「まだゴミが残っている」などなど

細かいルールとして、一人の手番が終わるたびに、非手番プレイヤーが順番に建設のみを行える、手番外建設フェイズが走るのだが、このフェイズの活用方法がよく分からなかった。7を振っても資源のバーストは起きないので、考えられるのは、線路を引いて妨害するか、発展カードを引いて自分の手番で即座に使えるようにするぐらいしかない。

結論としては、アメリカカタンはカタンではないしカタンという名前を使うのは既存のブランドを利用した名前詐欺でしかない。ゲーム自体は悪くなかったが、どうもあまり深く考えずに取り入れている要素も多いように思った(手番外建設フェイズなど)

2015-11-25

C++標準化委員会の文書のレビュー: P0080R0-P0089R0

P0080R0: Variant: Discriminated Union with Value Semantics

variantライブラリの提案。

P0081R0: A proposal to add sincos to the standard library

sinとcosを同時に計算してくれるsincosライブラリの提案。

ある値に対してsinとcosを同時に両方共計算したいことはよくある。同じ値を二度引数として別の関数に渡すのは間違いのもとである。また、x86などの一部のアーキテクチャには、sinとcosを同時に計算してくれる効率的な命令が用意されている。

GCCにあるsincosは、結果をポインターで受け取るが、ポインターが絡むとエイリアスなどの問題が出てくるので、提案されている文法は、単に値をpairで返す。

#include <utility>
namespace std {

pair<double, double> sincos(double x) noexcept;

// その他、floatなどのオーバーロード関数
}

std::tieを使えば、pairは意識せずにすむ。

#include <tuple>

void f( double x )
{
    double s, c ;

    std::tie( s, c ) = std::sincos( x ) ;
}

[PDF] P0082R0: For Loop Exit Strategies (Revision 1)

forループから、条件式がfalseになって抜けた時と、breakで抜けた時に実行されるブロック文を記述できるif for文の提案。

if for ( ... ; 条件 ; ... )
    {
        // for文のブロック文
    }
{
    // 条件がfalseになって抜けた時に実行される   
}
else
{
    // breakで抜けた時に実行される。
}

この論文筆者が主張するには、以下のようなコードより読みやすいらしい


for ( ; 条件 ; )
{
    // for文のブロック文
}

if ( !条件 )
{
    // 条件がfalseで抜けた
}
else
{
    // breakで抜けた
}

どうも必要性がわからない。そういう複雑なfor文は関数として切り分ける方が読みやすいし、lambda式のある現在、ライブラリベースの実装でも良い気がする。

[PDF] P0083R0: Splicing Maps and Sets (Revision 2)

std::listには、spliceという機能がある。これを使えば、あるlistコンテナーで管理されている要素を、所有権ごと別のコンテナーに移動することができる。


    std::list a { 1, 2, 3 } ;
    std::list b { 4, 5, 6 } ;

    a.splice( a.end() , b ) ;

    // a = { 1,2,3,4,5,6}
    // b = { }

たといint型のようにコピーやムーブの処理が軽くても、メモリの動的な確保と解放のコストは大きい。spliceは、所有権ごと要素を移すことで、不要なコストを削減できる。

しかしmapとsetには、このようなspliceがないし、簡単に提供できない。

そこで、連想コンテナーから要素の所有権を切り離せるextract、所有権が切り離された要素を管理するnode_ptr、node_ptrを受け取るinsertを追加する提案。

[PDF] P0084R0: Emplace Return Type

emplaceが構築したオブジェクトへのリファレンスを返すようにする提案。

emplaceは、コンテナーに挿入すべきオブジェクトを渡すのではなく、オブジェクト構築時の引数を渡す。しかし、構築後のオブジェクトを操作したい場合、コンテナーからそのオブジェクトをわざわざ引っ張り出してこないといけない。

struct X
{
    X( int, int, int ) ;

    void do_something() ;
} ;

int main()
{
    std::vector<X> v ;

    // 構築
    v.emplace_back( 1, 2, 3 ) ;

    // 構築後のオブジェクトを引っ張りだして操作
    v.back().do_something() ;
}

emplaceが構築後のオブジェクトへのリファレンスを戻り値として返すようにすれば、このコードは簡略化できる。

v.emplace_back( 1, 2, 3 ).do_something() ;

提案では、emplace_frontとemplace_backを変更するよう提案している。従来のfrontとbackは戻り値を返さないが、これはどうせオブジェクトを実引数で渡すので、コピー/ムーブされたオブジェクト

P0085R0: Oo... adding a coherent character sequence to begin octal-literals

新しい8進数リテラル0oを追加する提案。

0o11 ; // 10進数で9
0O11 ; // 9

既存のプレフィクス0から始まる8進数リテラルは、現代人には馴染みが薄く混乱のもとである。このリテラルは、まだコンピューターが原始的で、パーソナル・コンピューターといえば個人が8bit CPUや16bit CPUを使って自力で作っていた時代からの名残である。

この提案では、新しい8進数リテラルのプレフィクス0o(数字ゼロにアルファベットの大文字小文字のオー)を追加する。

新しい8進数リテラルの追加によって、8進数リテラルへの注目度を上げ、この新しい8進数リテラルを解説する時は、古い8進数リテラルへの注意も併記して問題認識を高め、いずれ既存の8進数リテラルをdeprecated扱いにして、除去することを目指している。

プレフィクス0oの8進数リテラルは、すでに、haskell, OCaml, Python 3.0, Ruby, Tcl Version 9でサポートされていて、ECMAScript 6もサポートする予定であるという。

[PDF] P0086R0: Variant design review

[PDF] P0087R0: Variant: a type-safe union without undefined behavior (v2)

[PDf] 0088R0: Variant: a type-safe union that is rarely invalid (v5)

ライブラリベースの型安全unionを実現するvariantライブラリの設計事情と、議論が別れている設計の差異について。

variantはinvalid/emptyな状態を許容すべきかどうか。その状態をクエリー可能にすべきかどうか。

純粋な数学的観点からinvalid/emptyなvariantは認めるべきではないという意見と、その場合、ユーザーが自前でダブルバッファリングをしなければならないので結局使いづらい。variantがダブルバッファリングを提供すべきだ。例外を投げないムーブのある型の場合、メタプログラミングでvariantのダブルバッファリングは除去できるし、将来的にはTransactional Memoryであらゆる型のダブルバッファリングが除去できるなどと豪語する壮大なお花畑意見まである。

[PDF] 0089R0: Quantifying Memory-Allocatiom Strategies

メモリ確保にローカルアロケーターを使うことによるパフォーマンス向上のベンチマークと考察

ローカルアロケーターを使い、個々のオブジェクトのデストラクターを呼ばずに、一括して解放することによる、パフォーマンスの向上の余地は十分にあると主張している。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-20

Mac OS Xで削除がとても難しいファイルを作成する方法

2013年の記事だが、Mac OS XとそのHFS+はこの上なくクソなので何があっても驚きはしないが、Mac OS Xで削除しにくいシンボリックリンクファイルを作成する方法があるらしい

OS X and the Unremovable File - galvanist

HFS+上で、削除しにくいシンボリックリンクを作成できる。

# be root, for example with: sudo -i
str1=$(python -c "print '1' * 255")
str2=$(python -c "print '2' * 255")
str3=$(python -c "print '3' * 255")
str4=$(python -c "print '4' * 253")
mkdir -p  $str1/$str2/$str3/$str4
ln -s ftw $str1/$str2/$str3/$str4/L

さて、このように作った以上、OS X v10.9では以下のコマンドでは削除できない。


# still as root...
unlink 1*/2*/3*/4*/L
unlink $str1/$str2/$str3/$str4/L
rm -rf 1*
rm -rf $str1
rm -rf $str1/$str2/$str3/$str4
rm -rf $str1/$str2/$str3/$str4/L
(cd $str1/$str2/$str3/$str4; unlink L)
(cd $str1/$str2/$str3/$str4; rm -rf L)

すべて、以下のようなエラーとなる。(読みやすさのために[...]で省略)

root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# ls
L
root# rm -f L
rm: L: No space left on device
root# df -H
Filesystem      Size   Used  Avail Capacity   iused     ifree %iused  Mounted on
/dev/disk1      250G   108G   142G    44%  26385563  34601956   43%   /
[...]

念の為、システムコールを直接呼び出すコードを書いてみる。/tmp/fixit.cにおいてあるとする。

#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char* argv[]) {
    printf("Unlink returned %i\n", unlink("L"));
    perror("Error was");
    return 0;
}

実行してみるが、

root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# gcc -o /tmp/fixit /tmp/fixit.c 
root# /tmp/fixit 
Unlink returned -1
Error was: No space left on device

ENOSPCだと。unlink(2) Mac OS X Developer Tools Manual Pageに返すとは書かれていないエラーだぞ。

状況は複雑だ。

  • 一般ユーザーが作ったのならば、一般ユーザーは、rm -rfで消すことができる
  • 一般ユーザーが作ったのならば、rootは消すことができない。おかしい
  • rootが作ったのならば、rootは消すことができない
  • rootが作ったのならば、通常通りの権限により、一般ユーザーは削除が行えない
  • rootが作ったものであっても、chmod -hとchown -hで権限を変えれば、一般ユーザーにも消せるはずである
  • rootが作ったものを、chmod -hとchown -hしようとすると、ENOSPCが返る。
  • rootが作ったものに対し、"mkdir -p some/containing/paths; mv 1111* some/containing/paths/"は動くが、その後、"rm -rf some"しても動かない。

Workaround

なぜか、パスはシンボリックリンクを作るには短いが、削除するには長すぎるようだ。パスを短縮すれば消せる。

root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# ls
L
root# mv /private/tmp/1* /private/tmp/one
root# pwd
/private/tmp/one/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# rm L
root# ls
root# rm -rf /tmp/one
root# 

workaroundは十分なのか。

そういうわけで、一応workaroundはあるのだが、しかし疑問がある。

  • アンチウイルスソフトウェアはマルウェアがこのような方法で保存されていた場合、ちゃんと消せるよう作られているのか? アンチウイルスソフトウェアはすでにchflags(2)の問題に対処しなければならないが、こういうパスと複数のchflagsで構成されていた場合はどうか
  • マルウェアや悪意ある人物がディスクをこのようなもので埋め尽くした時に、rm -rfの代替品となるツールは存在するのか?

ちなみに、これは2013年の記事だが、Yosemiteまでは同じ問題があるそうだ。El Capitainだと、ファイルパスが長すぎて作成できないエラーが返るらしい。そもそも、Mac OS XのカーネルであるXNUのファイルパスの上限が2024文字なのに2025文字のファイルパスを作成できてしまうのがおかしいようだ。

C++標準化委員会の文書のレビュー : P0070R0-P0079R0

P0070R0: Coroutines: Return Before Await

現在のコルーチン提案では、関数がコルーチンかどうかを宣言から判定できない。そのような判定をさせるタグ機能は議論の結果却下された。しかし、awaitかyieldをみるまで関数がコルーチンかどうかわからないのは不便なので、コルーチン関数では、通常のreturnキーワードを使ったreturn文のかわりに、特別なco_returnキーワードを使ったreturn文の使用を強制させるようにしようという提案に対して、その必要はないとする論文。

理由は、MSVCやGCCは、すでに最適化のために、関数の全体を見るまで意味解析を行わないようになっているので、そんな技術的制約上の機能は必要ないとのこと。

P0071R0: Coroutines: Keyword alternatives

現在のコルーチンの提案では、awaitとyieldに当たるキーワードが未定である。理想世界においては、awaitとyieldというキーワードは機能をこれ以上なく明白に指し示したものである。しかし、現実世界においては、awaitやyieldのような既存のコードと衝突するようなキーワードを導入できない。

これに対して、いくつかの解決策が示されている。

ソフトキーワード:std名前空間に暗黙に宣言されている名前で、他の名前と衝突しない場合、キーワードとして認識される。

マジック関数: std名前空間内にある特別な関数で、コンパイラーが認識する。

問題は、文法が関数呼び出し式になってしまうので、すこし汚い。

await expr ;

が、

std::await( expr ) ;

になってしまう。

その他のキーワード案: exportキーワードが余っているではないか。yieldexpr/yield_exprはどうか。coyield/co_yieldはどうか。

論文では、awaitとyieldに変わる良いキーワードはないとした上で、ソフトキーワードかマジック関数のどちらかを採用すべきだとしている。

[PDF] P0072: Light-Weight Execution Agents

スレッドよりも制約の強い実行媒体(Execution Agent、スレッドプール、ファイバー、GPGPU、SIMDなど)を扱うために、実行媒体とその保証について定義する提案。

[PDF] P0073R0: On unifying the coroutines and resumable functions proposals

コルーチンとレジューム可能関数を統合したことについて

P0074R0: Making std::owner_less more flexible

std::onwer_lessという関数オブジェクトがある。これは、2つのshared_ptrとweak_ptrが、同じオブジェクトを所有しているかを判定してくれる機能を提供している。しかし、このインターフェースが悪いため、とても単純な例でしか動かない。

  shared_ptr<int> sp1;
  shared_ptr<void> sp2;
  shared_ptr<long> sp3;
  weak_ptr<int> wp1;

  owner_less<shared_ptr<int>> cmp;
  cmp(sp1, sp2);   // error, doesn't compile
  cmp(sp1, wp1);
  cmp(sp1, sp3);   // error, doesn't compile
  cmp(wp1, sp1);
  cmp(wp1, wp1);   // error, doesn't compile

最初の例は、owner_less<shared_ptr<void>>を使うことで解決できるが、単純にそれだけを使ってもコンパイルが通らない。なぜならば、sp1はshared_ptr<void>にもweak_ptr<void>にも変換可能だからだ。そのため、明示的に変換してやらなければならない。

owner_less<shared_ptr<void>> cmpv;
cmpv(shared_ptr<void>(sp1), sp2);

これは冗長でわかりにくいだけではなく、一時オブジェクトを作り出し、参照カウンターを増減させる。

shared_ptr::owner_beforeとweak_ptr::owner_beforeはどちらもshared_ptr<A>とweak_ptr<B>といった異なる型同士での比較を許しているのに、owner_lessは無用の制限をかけている。

owner_lessを改良して、上のコードが全てコンパイルが通るようにする提案。

[PDF] P0075R0: Template Library for Index-Based Loops

インデックスベースループの並列版をライブラリとしてParallelism TSに付け加える提案。

void ( int * p1, int * p2, std::size_t size )
{
    std::for_loop( std::seq, 0, size,
        [&]( auto i )
        {
            p1[i] = p2[i] ;
        }
    ) ;
}

このコードは以下のように書くのと同等だ。

void ( int * p1, int * p2, std::size_t size )
{
    for ( std::size_t i = 0 ; i != size ; ++i )
    {
        p1[i] = p2[i] ;
    }
    
}

他のParalellism TSのアルゴリズムのように、std::secをstd::parに変更すると、並列実行版にある。

for_loop_stridedは、インクリメントの刻み幅を設定できる。

for_loop_strided( std::par, 0, 100, 2, []( auto i ) { } ) ;

iは0, 2, 4, 6とインクリメントされていく。

提案はfor_loopとともに使えるreductionとinductionをサポートしている。

reductionはOpenMPの文法を参考に、純粋にライブラリベースで使えるように設計されている。

float dot_saxpy(int n, float a, float x[], float y[]) {
    float s = 0;
    for_loop( par, 0, n,
        reduction(s,0.0f,std::plus<float>()),
        [&](int i, float& s_) {
            y[i] += a*x[i];
            s_ += y[i]*y[i];
        });
    return s;
}

reductionは、reduction( var, identity, op )のように使う。それぞれ、reductionの結果をうけとるlvalue, reduction操作のためのidentity value, reduction操作となる。

reductionの個別のsのコピーはfor_loopの関数オブジェクトに追加の引数として与えられる。for_loopはvariadic templatesを利用していて、reductionやinductionを受け取れるようになっている。最後の実引数が関数オブジェクトとなる。

ループのイテレーションごとに異なる可能性のあるsのコピーが渡され、それに対してreduction操作、最後にすべてのsのコピーがsに対してreduction操作が行われる。結果として、以下のコードを実行したのと同じ結果(ただしシーケンスとreductionの順序の制約がゆるい)が得られる。

float serial_dot_saxpy (int n, float a, float x[], float y[]) {
    float s = 0;
    for( int i=0; i<n; ++i ) {
        y[i] += a*x[i];
        s += y[i]*y[i];
    }
    return s;
}

簡便化のために、identitiy valueがreduction操作に合わせてT()やT(1)など無難なものになった上に、reduction操作も固定されている、reduction_plusなどが用意されている。上記の例は、reduction_plus( s ) と書くこともできる。

inductionもreductionと同じように指定する。

float* zipper(int n, float* x, float *y, float *z) {
    for_loop( par, 0, n,
        induction(x),
        induction(y),
        induction(z,2),
        [&](int i, float* x_, float* y_, float* z_) {
            *z_++ = *x_++;
            *z_++ = *y_++;
        });
    return z;
}

この分野の知識が乏しいのでinductionの意味がよくわからない。以下のコードとシリアル実行という点を除いては同じ意味になるようだが、単にlambda式でキャプチャーしてはダメなのだろうか。

float* zipper(int n, float* x, float *y, float *z) {
    for( int i=0; i<n; ++i ) {
        *z++ = *x++;
        *z++ = *y++;
    }
    return z;
}

[PDF] P0076R0: Vector and Wavefront Policies

Parallelism TS(STLのアルゴリズムに並列実行版のオーバーロードを追加する規格)に、新しい実行ポリシーとしてvecとunseqを追加する提案。

unseqはseqより実行時特性の制約がゆるいが、実行は単一のOSスレッド上に限定される。vecはシーケンスの制約がunseqより強い。vecはSIMDなどのベクトル演算を想定している。

P0077R0: is_callable, the missing INVOKE related trait

指定した型が指定した実引数型で呼び出して指定した戻り値の型を返すかどうかを調べられる、is_callable traitsの提案。

template <class, class R = void> struct is_callable; // not defined

    template <class Fn, class... ArgTypes, class R>
      struct is_callable<Fn(ArgTypes...), R>;

    template <class, class R = void> struct is_nothrow_callable; // not defined
    template <class Fn, class... ArgTypes, class R>
      struct is_nothrow_callable<Fn(ArgTypes...), R>;

以下のように使う。

void f( int, double ) ;

constexpr bool b = std::is_callable< f ( int, double ), void >::value ; // true

is_callable< Fn( args ... ), R >は、INVOKE( declval<Fn>(), declval<args>()..., R )が合法かどうかを返す。

上記の条件に加えて、無例外保証も調べるis_nothrow_callableもある、

[PDF] P0078R0: The [[pure]] attribute

[[pure]]属性を追加する提案。

[[pure]]属性は関数に指定することができる。指定された関数はpureとなる。pure関数には様々な制約がある。

pure関数は、与えられた入力に対して出力を返す。同じ入力が与えられた場合、必ず同じ出力を返す。副作用を発生させてはならない。戻り値の型がvoidであってはならない(純粋に数学的な関数は常に戻り値を返す)

現在の文面案は以下の通り。

関数fがpureである場合、数学的な関数を実現している。すなわち、(a) 同じ値の実引数に対して、fは常に同じ答えを返す。(b) fはクライアントコードと実引数リストと戻り値のみを使ってやり取りする。(c) 常に呼び出し元に戻る。(d) fはその実行外において観測可能な副作用を発生させない。関数gの本体の中の文Sがpureである場合、Sが実行されたならば、pure gの挙動から外れた挙動を示してはならない。pure関数、あるいは文の反対は、impureである。すべての関数と文は、pureであると指定されない限り、impureとなる。

[ 例:関数fは、以下の場合、pureではない。

  • 実引数が値で渡されていない
  • グローバルメモリへの読み書きアクセス
  • 戻り値の型がvoid
  • impure関数を呼び出す
  • 呼び出し元や他のスレッドがfによる変更を感知できること、たとえば、

    1. 関数にローカルではない変数やデータ構造に依存するか書き変える
    2. 動的にメモリーを確保する
    3. 例外をcatchしないで外に投げる

pure関数には様々な利点がある。pure関数を使った式は除去したりmemoizationにより簡易化できる。同期やスレッドセーフ、コード順序の変更も大胆に行えるので、最適化の余地が広がる。pure関数はテストしやすい。

pure関数には問題もある。副作用を発生させることができないので、I/Oや動的メモリ確保ができないし、impure関数を呼び出すこともできない。コンパイラーはpure指定された関数はpureであるという前提のもとコード生成を行うので、pure関数に副作用を発生させるバグがある場合、不可思議な挙動を示し、特定が困難になるかもしれない。

論文は既存のプログラミング言語におけるpure関数の実装例も紹介しているので興味のある人は読むと良い。

P0079R0: Extension methods in C++

統一感数記法に変わる軽量な拡張メソッドの提案。

統一感数記法は、メンバー関数呼び出しとフリー関数呼び出しの文法を統一し、どちらで呼び出しても良いようにするものであった。

struct A { } ;
void foo( A & ) ;

struct B { void foo() ; }

int main()
{
    A a ;
    a.foo() ; // OK

    B b ;
    foo(b) ; // OK 
} 

目的としては、ジェネリックなコードで、型によってメンバー関数とフリー関数によって呼び出す文法を変えなければならないのは面倒なので、どちらで呼び出してもコンパイラーが勝手に変換してくれるようにしようというものだ。

しかし、この機能はあまりにも大きすぎる変更なので、既存のコードに問題を引き起こす恐れがある。そこで、この提案では、もう少し軽量な、明示的なopt-inが必要となる機能、拡張メソッドを提案している。

拡張メソッドは、フリー関数で、第一引数として明示的なthisポインターを取る。仮引数名は必ずthisとする。

struct A { } ;

// 拡張メソッド
void foo( A * this, int ) ;

int main()
{
    A a ;
    a.foo( 0 ) ; // OK
}

オーバーロード解決では、メンバー関数と拡張メソッドでは、メンバー関数のほうが優先される。


struct A
{
    void f() ;
} ;

void f( A * this ) ;

int main()
{
    A a ;
    a.f() ; // メンバー関数
}

拡張メソッドは、アクセス指定においては特別扱いされない。

struct A
{
private :
    int x ;
} ;

void f( A * this )
{
    this->x ; // エラー
} 

コンセプトと組み合わせることで、メンバー関数を持っている場合のみ呼び出す拡張メソッドが書ける。

template < typename T >
concept bool has_foo()
{
    return requires( T & t )
    {
        { t.foo() } -> void ;
    } ;
}

void foo( has_foo * this )
{
    this->foo() ;
}

C#にある拡張メソッドを参考にしているらしい。

なんだか素朴な案だ。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-18

Minecraftクライアントからdockerの管理ができるminecraftサーバー: Dockercraft

docker/dockercraft

公式の未改変Minecraftクライアントから接続してDockerの管理ができるMinecraft互換サーバー、Dockercraftが公開されている。

Dockercraft

Dockercraftの実行方法

1. Minecraftのインストール: Minecraft

Minecraftクライアントは改変していないので、公式リリースをそのまま使える。

2. Dockercraftイメージをpullするかビルドする。

docker pull dockercraft

もしくは、

git clone git@github.com:docker/dockercraft.git
docker build -t dockercraft dockercraft

3. Dockercraftコンテナーを実行する

docker run -t -i -d -p 25565:25565 \
-v /var/run/docker.sock:/var/run/docker.sock \
--name dockercraft \
dockercraft

Docker remote APIを使うには/var/run/docker.sockをコンテナー内でマウントする必要がある。

Minecraftサーバーのデフォルトのポートは25565だ。他のポートがいいのであれば、"-p <port>:25565"

4. Minecraft > Multiplayer > Add Serverを開く

サーバーアドレスはDockerホストのIP。デフォルトのものを使っているのであればポート番号を指定する必要はない。

Docker Machineを使っているのであれば、"docker-machine ip <machine_name>"

5. サーバーにjoinする。

少なくともひとつのコンテナーが世界に見えるはずだ。それはDockercraftサーバーをホストしているものである。

レバーとボタンを操作することで、コンテナーをスタート、ストップ、リムーブできる。Minecraftのチャットウインドウから一部のDockerコマンドがサポートされている。チャットウインドウはTキー(デフォルト)か/キーで表示される。

実装

Mnecraftクライアント自体は改変していない。操作は全てサーバーサイドで行われている。

Minecraftサーバーには、Cuberite.orgを用いた。C++で書かれた独自のMinecraft互換ゲームサーバーだ。GitHubレポジトリ

サーバーはLUAで書かれたスクリプトをプラグインとして受け付ける。そこでDocker用のものを書いた。(world/Plugins/Docker)

残念ながら、このプラグインと通信するいいAPIがない。ただしwebadminがあり、プラグインは"webtabs"に応答できる。

Plugin:AddWebTab("Docker",HandleRequest_Docker)

つまり、プラグインは、http://127.0.0.1:8080/webadmin/Docker/Dockerに送られたPOSTリクエストを見ることができる。

Goproxy

Docker remote APIのイベントはGoで書かれた小さなデーモン(go/src/goproxy)によってLUAプラグインに送られる。

func MCServerRequest(data url.Values, client *http.Client) {
    req, _ := http.NewRequest("POST", "http://127.0.0.1:8080/webadmin/Docker/Docker", strings.NewReader(data.Encode()))
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    req.SetBasicAuth("admin", "admin")
    client.Do(req)
}

デーモンにリクエストを送るため、LUAプラグインからgoproxyバイナリを匹数付きで実行することもできる。

function PlayerJoined(Player)
    -- refresh containers
    r = os.execute("goproxy containers")
end

貢献

Dockercraftをハックしたいならば、Docker's contributions guidelinesを参照。

これで、仕事中にMinecraftで遊んでいても、「Dockerの管理をビジュアライズしてくれる便利なツールなんです」という言い訳がたつ。

「なるほど、しかしなぜ大規模なクラフトをする必要があるんだね?」
「Docker操作を自動化するために必要な論理回路を組んでいます」

2015-11-16

C++標準化委員会の文書のレビュー: P0060R0-P0069R0

P0060R0: Function Object-Based Overloading of Operator Dot

operator .をオーバーロード可能にする提案。

この提案は、リファレンスを返す提案とは異なり、コンパイラーが関数オブジェクトを生成する提案となっている。

operator .をオーバーロードしてるユーザー定義型がある。

struct X
{
    // なにもしない
    template < typename T >
    void operator .( T && ) { }
}

そのような型の非staticデータメンバーの名前検索(たとえば.some_name)をして、名前が見つからない場合、コンパイラーは以下のような型を生成し、そのオブジェクトをoperator .のオーバーロード関数の実引数に渡す

struct synthesized_function_type {
    template<class T>
    auto operator()(T&& t) -> decltype(t.some_name) noexcept(t.some_name) {
        return t.some_name;
    }
};

その結果、以下のように書ける。

X x ;
x.hoge ; // OK
x.hage ; // OK

メンバー関数の場合は、以下のような型が生成される。

struct synthesized_function_type {
    template<class T, class... Args>
    auto operator()(T&& t, Args&&... args) -> decltype(t.some_name(std::forward<Args>(args)...)) noexcept(t.some_name(std::forward<Args>(args)...)) {
        return t.some_name(std::forward<Args>(args)...);
    }
};

これを使って、例えばスマートリファレンスを作成できる。

template < typename T >
class Ref
{
    T t ;
public :
    template < typename F >
    auto operator . ( F && f )
    {
        return f( t ) ;
    }

    template < typename F, typename ... Args >
    auto operator . ( F f, Args && ... args )
    {
        return f( std::forward<Args>( args ) ... ) ;
    }
} ;

struct Point
{
    int x, y ;
    void func( int x ) { }
} ;

Ref<X> r ;

r.x ;
r.y ;
r.func( 0 ) ;

P0061R0: Feature-testing preprocessor predicates for C++17

SG-6が推奨している機能テストマクロに含まれている、__has_includeと__has_cpp_attributeを正式にC++17に提案している。

__has_includeは指定したヘッダーファイルがあるかどうかを調べることができる。__has_cpp_attributeはattributeが存在するかどうか調べることができる。

#ifdef __has_include(<optional>)
#   include <optional>
#else
#   include "my_optional"
#endif 

P0062R0: When should compilers optimize atomics?

atomicではすべての最適化が無効になると考えている者もいるが、atomicは最適化できる。たとえば、load( x, memory_order_x ) + load( x, memory_order_x )は、2 * load( x, memory_order_x )に変換することができる。しかし、非volatileなatomic loadが使われてはいないからといって、消してしまうのは異論が多い。

この論文では、c++std-parallelで議論された、異論のある最適化についてまとめている。

// 途中経過表示用の変数
atomic<int> progress(0);

f() {
    for (i = 0; i < 1000000; ++i) {
        // 処理
        ++progress;
    }
}

このようなコードがあるとする。atomic変数progressは処理の進行具合を記録していて、他のスレッドはこの変数を参照してユーザーに処理の進行具合をプログレスバーで表示している。

過剰に最適化するコンパイラーは、以下のようにコードを変換するかもしれない。

atomic<int> progress(0);
f() {
    int my_progress = 0; // レジスタ割り当てされた非atomic変数
    for (i = 0; i < 1000000; ++i) {
        // ...
        ++my_progress;
    }
    progress += my_progress;
}

コードがこのように変換されると、他のスレッドがprogressを参照した時に、0か999999しか見えない。

他には以下のような例がある。

x.store(1); // アトミック操作
while (...) { … } // ループ

コンパイラーは以下のように変換するかもしれない。

while (...) { … }
x.store(1);

これも規格上問題ないが、ループを始める前にアトミック操作を行って他のスレッドに通知したいと考えるプログラマーの意図とは異なるだろう。

Peter Dimovによれば、過剰な最適化は好ましいという。例えば、shared_ptrのカウンターの増減を除去できる。

Paul McKennryによれば、上記のプログレスバー問題を解決するのによく使われる手法は、アトミック変数をvolatile修飾することだという。しかし、これでは問題のない好ましい最適化まで抑制されてしまう。コンパイラーがそこまで過剰に最適化を行わない場合はパフォーマンスの損失につながる。

論文筆者のHans Boehmの考えでは、コンパイラーはアトミック変数を過剰に最適化するべきではない。属性などで過剰に最適化させる方法を提供すべき。としている。

過剰な最適化は、shared_ptrの例のようにとてもよいこともあれば、プログレスバーの例のようにとても悪いこともある。コンパイラーにとって、良い悪いは判断できない。判断できないのであれば、どちらがわにも倒さずにコードをそのままにしておくべきだ。

とはいえ、過剰な最適化の利点はあるので、そのための方法を提供すべきだ。

論文は最後に、規格の文面に追記する注釈案を載せている。

P0063R0: C++17 should refer to C11 instead of C99

C++規格が参照するC規格をC99からC11に引き上げる提案。

単に参照先を買えるだけではない変更も考察されている。シグナルハンドラー内ではthread_local変数は使えない。新たに追加されたCヘッダーに対してはC++規格からは参照しない。Atomicsについては、_Atomic(T)がatomic<T>になっていると嬉しい。C言語に新しく追加された有益な機能のC++版がないなど。

[PDF] P0065R0: Movable initializer lists, rev. 2

initalizer_list<T>のbegin/endの戻り値の型は、const T *である。そのため要素をムーブできない。この提案は、非constなT *を返すbegin/endを持ったown_initializer_list<T>を提案している。

初期化リストから推定される型Tが、非トリビアルなムーブコンストラクターを持っている場合は、型はown_initializer_list<T>になる。そうでない場合は、initializer_list<T>になる

// std::initializer_list<int>
auto x = { 1, 2, 3 } ; 
// std::own_initializer_list<std::string>
auto y = { std::string("hello") } ;

[PDf] P0066R0: Accessors and views with lifetime extension

一時オブジェクトをリファレンスに束縛すると、一時オブジェクトの寿命がリファレンスの存在期間中延長されるが、現行の規程は問題が多い。

std::string const & f( std::string const & ref ) { return ref ; }

int main()
{
    // 寿命が延長されない。
    auto const & ref = f( std::string("hello") ) ;
}

initializer_listだけはこのような一時オブジェクトの寿命延長をコンパイラーマジックによって行っている。

一時オブジェクトの寿命延長を関数やクラスのサブオブジェクトを経由してもユーザーが行えるようにする寿命修飾子を提案しているのだが、どう考えても非現実的な机上の空論のクソ機能にしか思えない。ABIに変更をもたらすのもクソだ。真面目に読んで損をした。

P0067R0: Elementary string conversions

整数と文字列、浮動小数点数と文字列間の相互変換のみを行うライブラリ、to_string, from_stringの提案。

JSONやXMLなどのテキストベースのフォーマット利用が拡大している現代では、数値と文字列間の高速な変換が必要である。変換はinternationalizationを行う必要はない。

そのためには、以下の要件を満たす必要がある。

  • フォーマット文字列の実行時パースを行わない
  • インターフェース上動的メモリ確保をしない(大抵のインターフェースは必要とする)
  • localeを考慮しない
  • 関数ポインターを経由した関節アクセスを行わない
  • バッファーオーバーランを防ぐ
  • 文字列をパースするとき、エラーと有効な数値は区別できる
  • 文字列をパースするとき、空白文字などが静かに無視されたりしない

既存の標準ライブラリで提供されている方法はすべて、localeに依存している。

また、変換処理は、教科書通りの実装では速くない。高速な実装方法が多数ある。

この提案では、極めて原始的なインターフェースを採用している。char *型のイテレーターベースのインターフェースだ。数値を文字列に変換するにはto_string、文字列を数値に変換するにはfrom_stringを使う

char * to_string(char * begin, char * end, T value, その他の引数) ;

T型の数値valueを文字列に変換したものが、[begin, end)に書き込まれる。戻り値は書き込んだ最後の要素の一つ次のポインターになる。文字列を書き込むスペースが足りない場合、endが返される。スペースが足りない場合に書き込まれる内容は未規定である。

整数型は以下のような関数になる。

char * to_string(char * begin, char * end, T value, int base = 10);

baseの範囲は2から36までで、10から36までの数値はaからzで表現される。valueが負数の場合、マイナス符号が先頭に書き込まれる。

浮動小数点数型は、変換する文字列のフォーマットごとに、以下のような2つの関数が提供されている

31415e-4のようなフォーマットの文字列に変換

char * to_string(char * begin, char * end, T value);

valueが負数の場合はマイナス符号が先頭に書き込まれる。文字列は10進数で、整数部が少なくともひと桁あり、オプショナルで小文字eに続いてexponentが記述される。表現は最小文字数かつ、from_stringによって元のvalueが正確に復元できるものとなる。valueが無限大の場合、infか-infとなる。NaNの場合、nanか-nanとなる。

3.1415のようなフォーマットの文字列に変換

char * to_string(char * begin, char * end, T value, int precision);

小数部の桁数はprecisionになる。

文字列を数値に変換するfrom_stringは以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec, 残りの引数 );

戻り値はパターンにマッチしない最初の文字へのポインター。

整数型に変換する場合は以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec, int base = 10);

浮動小数点数型に変換する場合は以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec);

文字列に変換する際のinfやnanも正しく認識される。

[PDF] P0068: Proposal of [[unused]], [[nodiscard]] and [[fallthrough]] attributes

タイトル通り、[[unused]], [[nodiscard]], [[fallthrough]] attributeの提案。

[[unused]]

[[unused]]が指定されたエンティティは、何らかの理由で使われていないように見えるという意味を持つ。これによって、実装による未使用警告を抑制できる。

int x ; // 警告、xは未使用
[[unused]] int x ; // 警告が抑制される

[[nodiscard]]

[[nodiscard]]が指定された関数は、戻り値がdiscardされてはならないことを意味する。戻り値が捨てられた場合、警告が出る。

[[nodiscard]] bool do_something_that_may_fail() ;

int main()
{
    do_something_that_may_fail() ; // 警告、戻り値が捨てられてい

    // OK
    if ( do_something_that_may_fail() )
    {
    }
    else
    {
    }
}

[[nodiscard]]が型に指定された場合、その型を戻り値の型に使う関数が[[nodiscard]]指定される。


[[ nodiscard]] class error_code { ... } ;

// 関数fは[[nodiscard]]指定される
error_code f() ;

[[fallthrough]]

[[fallthrough]]は文のように記述される。これをfallthrough文という。

[[fallthrough]] ;

[[fallthrough]]はcaseラベルの直前に書く。これによって、複数のケースを突き抜けたコードに対しての警告を抑制できる。

switch( x )
{
case 0 :
    do_something() ;
    // 警告、fallthrough
case 1 ;
    do_something() ;
    [[fallthrough]]
    // 警告なし
case 2 ;
    do_something() ;

} 

いい提案だ。

[PDF] P0069R0: A C++ Compiler for Heterogeneous Computing

GPU用に現在提案されているGPUのサポートも視野に入れた機能をC++コンパイラーに実装した経験の報告書。

開発したのコンパイラー、HCC(Heterogeneous C++ Compiler)は、Clang/LLVMベースのコンパイラーだ。HCCはホストプロセッサー用のコード(x86)とアクセラレーター用のコード(AMD GPU/HSA)を同時に生成する。HCC自体は公開されていないようだ。

問題となるのは、コードのどこの部分をどちらのプロセッサー用のコードで生成するかだ。すべてのコードをすべてのプロセッサー向けにコード生成を行うと、コンパイル時間が増大する上に、プロセッサーごとの特性もあるので、逆に遅くなる。

C++提案では、特定の領域に対して機能を制限してコンパイラーにヒントを与える機能があるが、これはあまり役に立たなかったと報告している。

HCCがかわりに開発したのは、プログラマーがコンパイラーがコード中の並列部分を指示できる機能云々

なんだかOpenMPのようだ。

モダンなGPUはC++の機能のほとんどを実行できるようになった。たとえば、間接的な関数呼び出し、バイト単位のメモリアクセス、基本型、アライメントされていないメモリへのアクセス、スタック、gotoなどだ。

HCCはC++のほとんどの機能をアクセラレーター向けにコード生成できる。例外はtry/catchブロックなどだ。try/catchブロックの実装は技術的に可能であるが、現在のところ実装していない。論文著者は、アクセラレーター向けにC++のサブセットを定義する必要はないと報告している。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0