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