2016-08-31

C++標準化委員会の文書: P0360R0-P0360R9

[PDF] P0360R0:SG14: Low Latency Meeting Minutes 2016/02/17-2015/05/25

SG14 低レイテンシー会議の議事録

[死んで欲しいPDF] P0361R0: Invoking Algorithms asynchronously

Parallelism TSで提案されたアルゴリズムの並列実行を拡張する形で、アルゴリズムを非同期実行を追加する提案。

従来のシーケンシャル実行のアルゴリズムの使い方は以下で、

Container c ;

sort( begin(c), end(c) ) ;

Parallelism TSによって、以下のようにかける。

Container c ;


// シーケンシャル実行
sort( seq, begin(c), end(c) ) ;
// 並列実行
sort( par, begin(c), end(c) ) ;
// 並列ベクトル実行
sort( par_vec, begin(c), end(c) ) ;

この提案は、Parallelism TSを拡張する形で、アルゴリズムの非同期実行版を追加する。

Container c ;


// シーケンシャル非同期実行
auto f = sort( seq(task), begin(c), end(c) ) ;
f.get() ;

// 並列非同期実行
auto f = sort( par(task), begin(c), end(c) ) ;
f.get() ;

// 並列ベクトル非同期実行
auto f = sort( par_vec(task), begin(c), end(c) ) ;
f.get() ;

非同期実行版の選択は、実行ポリシーのoperator ()にtaskシングルトンを与えることで行える。非同期実行版のアルゴリズムは、通常の戻り値の型をTとした場合、future<T>を戻り値の型として返す。

戻り値に対してメンバー関数getを呼び出すと、非同期処理の完了に同期する。

なかなかいい提案だ。自然に拡張できている。

例えば以下のようなコードがあったとして

template<typenameBiIter, typenamePred>
pair<BiIter, BiIter>
gather( BiIterf, BiIterl, BiIterp, Predpred )
{
    BiIter it1 = stable_partition( f, p, not1(pred) );
    BiIter it2 = stable_partition( p, l, pred );
    return make_pair(it1,it2);
}

これを非同期実行版に書き換えると、以下のようになる。


template<typenameBiIter,typenamePred>
future<pair<BiIter,BiIter>>
gather_async(BiIterf,BiIterl,BiIterp,Predpred)
{
    future<BiIter>f1=stable_partition(par(task),f,p,not1(pred));
    future<BiIter>f2=stable_partition(par(task),p,l,pred);
    return when_all(f1,f2).then(
        [](tuple<future<BiIter>,future<BiIter>>p)
        { return make_pair(get<0>(p).get(),get<1>(p).get());}
    );
}

これはわかりにくいが、現在提案中のコルーチンを使えば、


return make_pair( co_await f1, co_await f2);

こうできる。

[PDF] P0362R0: Towards support for Heterogeneous Devices in C++ (Concurrency aspects)

SIMDやGPGPUのようなHeterogeneousなデバイスを利用するには、現在のところOpenMPやOpenCLのように、C++ではないプロプライエタリな言語を用いている。C++はこのようなデバイスをサポートする力があり、プロプライエタリな言語を使わずに標準規格で定められた言語のみを使って移植性の高いプログラミングできるのは価値が高い。

ホストCPUと、Heterogeneousなデバイスを、単一のC++のソースファイルで利用したい。OpenCLのように分離されている場合、型システムなどのエラーチェックが行えない。

この提案では、KhronosのSYCLを元に、デバイスをサポートする方法について考察している。文書中でサポートするにあたって興味深い問題がふたつある。

lambda式のABI問題。

デバイスで実行するコードを渡すのには、lambda式が最も都合がよい。ただし、lambda式のABIは規格で定められておらず、複数のC++コンパイラーを使う場合に問題になる。

lambda式は規格で名前が規定されていないので、名前マングリングが実装ごとに異なる。lambda式がキャプチャーした変数に対する、クロージャーオブジェクト内でのデータメンバーのレイアウトも規格で規定されていないので、実装ごとに異なる。

このために、コア言語で規定するか、静的リフレクションが必要だとしている。

非フラットなアドレス空間の問題

CPUはメモリに対してフラットなアドレス空間を持っている。ところが、GPUのメモリはフラットではなく、特性の異なる複数のメモリがあり、アドレス空間も複数ある。どうやってC++でサポートすればよいのか。

コア言語でアドレス空間を型システムに含める方法。アドレス空間を表現するクラスライブラリを経由してアクセスする方法。非効率的だがフラットなアドレス空間に見せかける方法などが考察されている。

[PDF] P0363R0: Towards support for Heterogeneous Devices in C++ (Language aspects)

HeterogeneousなデバイスをC++でサポートする際のlambda式のABI問題について少し深く掘り下げているが、基本的に内容はP0362R0からそれほど変わっていない。

[PDF] P0364R0: Report on Exception Handling Lite (Disappointment) from SG14

ゲーム、組み込み、金融分野が低レイテンシーについて議論するSG14から、例外処理の報告書。

例外にはオーバーヘッドがある。プログラムが実行中に例外を投げないとしても、依然としてオーバーヘッドがある。

例外のオーバーヘッドは確かに存在するのだが、現実的なソフトウェアにおけるコストが計測されたことは一度もない。このために、ソースコードが入手できるQuakeなどのエラー処理を例外を使うものに書き換えて計測する実験が行われてほしい。

ともかく、例外による予測できない処理時間の増加の可能性を避けるために、ゲームをコンパイルするときは、慣習的に例外は無効化されている。このために、例外を使わない設計のEASTLのようなものが開発されている。例外が使えないので、多くのC++ライブラリがゲーム業界では使えない。これによりC++利用者は分断され、C++の発展によろしくない。

文書は、SwiftやRustにおける例外は既存の言語の問題を認識したうえで改良がなされているとして、比較している。例外を外に投げる関数を呼び出すには、明示的な文法が必要となる。C++の例外をそのようにするのは利益が大きいが、何十年もの移行期間が必要だ。

また、組み込み業界からは、例外なき例外(exceptionless exception handling)という要望が出ている。これは、例外によるstack unwindingは行う者の、例外オブジェクトは扱わないというものだ。

例外オブジェクトが存在すると、そのオブジェクトのためのストレージを確保しなければならない。例外ハンドラーでは、オブジェクトの実行時に型に対して派生関係を比較するコードが必要だ。これは組み込みでは支払うことができないコストである。

[PDF] P0365R0: Report on SG14, a year later and future directions

SG14の設立経緯と注目している提案と将来性についての報告書。

SG14は、ゲーム、金融、組み込みの業界人がC++の低レイテンシーについて議論するために設立された。

その発端は、2014年のCPPCONで、筆者であるMichael Wongがパネルを務める議論において、カナダのゲーム会社の人間からC++をゲーム対応を改良することについて質問を受けたのがきっかけだ。

SG14は、市場初めて、ゲーム業界がその名前を名乗って業界を代表してC++に意見を表明する場所になった。というのも、C++標準化委員会は定期的に会議に参加して、会社、団体を代表して意見を表明し、提案し、議論し、コンセンサスを持ち帰って伝える役目を果たす人間が必要なのだが、ゲーム業界は厳しい納期に追われているために、そのような活動ができなかった。

SG14の目的は、制約のあるリソース、リアルタイムグラフィック、低レイテンシー、といった、ゲーム開発、金融、組み込みによくある要求の追求だ。

文書では、現在までにSG14が提案した文書を示している。

[PDF] P0366R0: Extending the Transactional Memory Technical Specification with an in_transaction Statement

トランザクションメモリーにトランザクション中かどうかを判定する機能を追加する提案。

トランザクショナルメモリーではトランザクションメモリーによる実行ができる関数とできない関数を型システムで区別している。transaction_safeな関数からtransaction-unsafeな関数を呼び出すことはできないし、transaction-unsafeな処理を行うこともできない。

問題は、ある関数が、トランザクション安全な実装をされているが、トランザクションの外で実行されたならば、もっと効率のいい実装ができる場合がある。このような実装を許可するために、トランザクションを実行中かどうかで実行時に条件分岐する機能がほしい。

この機能をどのように提供するかについて、提案は3つの案を示している。

ひとつ目の案はコア言語によるものだ。

何らかのキーワード(例えばin_transaction)を使う。

void* memcpy(void* dest, void* src, size_t n) transaction_safe {
    in_transaction {
        return memcpy_safe(dest, src, n);
    } else {
        return memcpy_asm(dest, src, n);
    }
}

in_transaction文のelseに続く文では、transaction-unsafeなコードを記述できるというものだ。

ふたつ目の案は、ライブラリによるものだ。

std::in_transaction()のようなboolを返す関数を使う。そして、falseが返った時に評価されるブランチでは、transaction-unsafeなコードを記述できる。


void* memcpy(void* dest, void* src, size_t n) transaction_safe {
    if (std::in_transaction()) {
        return memcpy_safe(dest, src, n);
    } else {
        return memcpy_asm(dest, src, n);
    }
}

3つめの案は、トランザクション実行の判定字体はふたつ目の案と同じくライブラリによるものだが、transaction-unsafeなコードを実行するには、コア言語に頼る。何らかのキーワード(例えばnot_in_transaction)で文を囲むことによって、transaction-unsafeなコードを記述できる。 


void* memcpy(void* dest, void* src, size_t n) transaction_safe {
    if (std::in_transaction()) {
        return memcpy_safe(dest, src, n);
    } else {
        not_in_transaction { return memcpy_asm(dest, src, n); }
    }
}

ふたつ目の案が最も良いように思われる。

[PDF] P0367R0: a C++ standard library class to qualify data accesses

汎用的なアクセッサーライブラリの提案。アクセッサーはラッパークラスとして提供される。

int x = 0 ;
auto ax = std::make_accessor< ... > ( x ) ;

// 元の型と同じように使える。
ax = 0 ;
int r = ax ;

このアクセッサーを通して、様々なアクセス方法の指定が行える。

例えば、1TBものdouble型の配列を初期化するとしよう。このような巨大な配列に対して先頭から最後まで値を書き込んでいく場合、わざわざそのストレージの極一部をキャッシュするのは無駄である。そこで、アクセスは一時的なものではないと指定することで、キャッシュを行わせないヒントを与えることができる。

// 1 TBもの配列
double a [2 < <37];
auto ac = make_accessor < non_temporal , write >( a ) ;
// 先頭から最後まで、42で始まるインクリメントされる整数値で初期化
std :: iota ( std :: par_vec , ac . rbegin () , ac . rend () , 42) ;

ある関数some_io()は、とても遅いI/O処理を行うので、値を返すのに時間がかかるとする。ただし、その値は、ほとんどの場合42であるとわかっているとする。

auto result = f( same_io() ) ;

その場合、f( 42 )をまず計算しておいて、some_ioが42以外の値を返した場合、関数fを計算し直すという投機的な実行が行える。そのためのヒントを出すことができる。

auto result = f ( make_accessor < likely > { some_io () , 42 }) ;

このように、様々なデータへのアクセスに対して、アクセス方法の指定を行える。

提案されているアクセス方法は極めて多岐にわたる。具体的なアクセス方法や、特定のパターンや特定のハードウェア機能に特化したアクセスの際のヒントを指定することができる。

異なるメモリ間のアクセス、read/writeアクセス、一時的ではないアクセス(キャッシュ回避)、エイリアシング、シーケンシャルアクセス、プリフェッチ、バーストモード、パイプラインアクセス、DMA、バスタイプ、アクセス幅、アドレスモード、アドレス変換、modulo addressing、アドレスビット設定(アドレスに使わないビットを指定できる)、トランザクショナルメモリー、予測。

だいぶ野望のある提案だが、ライブラリベースではなくてattributeではダメなのだろうか。

[PDF] P0369R0: 2017-07 Toronto ISO WG21 C++ Standard Meeting information

2017年7月に開催されるトロント会議の現地情報。

ドワンゴ広告

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

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

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

2016-08-24

npm、一見無意味なパッケージを消したら1000件ものパッケージが依存するパッケージであったことが判明

npmが一見無意味に思えるfsというパッケージをSPAMとみなして削除したところ、1000件ほどのパッケージが依存するパッケージだったので、削除を取り消した。

npm, Inc. Status - "fs" unpublished and restored

今日、数分ほど、"fs"というパッケージが、ユーザーからSPAMであるという報告を受けて、レジストリから非公開にされた。これは現在復旧されている。これは私(@seldo)による人為的なミスである。私は非公開が安全であるかを確認する内部のガイドラインに従っていなかった。ビルドが阻害されたユーザーに謝罪する。

詳細:"fs"というパッケージは、無意味なパッケージである。これは単に"I am fs"をログに残して終了する。このパッケージが何らかのモジュールの依存に含まれるべき理由は一切ない。しかし、1000件ほどのパッケージが、誤って"fs"に依存している。おそらく、"fs"という組み込みのnodeモジュールを使おうとしているのだろう。この理由により、我々は同モジュールを非公開ではなく、ガイドラインに従って、deprecated扱いにした。

もし、既存のモジュールが"fs"に依存しているのであれば、安全に消すことができる。実際、消すべきである。仮に消さなかったとしても、今後も問題なく動作はする。

npmやnodejsが悪いのか、nodejs界隈のユーザー層が悪いのか判断に苦しむが、いずれにせよnode.jsはクソであることがわかる。

2016-08-23

C++17ドラフトのiostreamに入った変更点

C++17のドラフトには、現在までに、iostreamへの変更点として、以下のような変更が加えられている。

P0004R1.HTML#0: Remove Deprecated iostreams aliases

C++98の時点でdeprecated扱いだった一部のメンバーが、とうとう削除された。最初の正式なC++規格の時点ですでにdeprecated扱いだったライブラリが、ようやく削除されたことになる。

ios_baseからは、以下のメンバーが削除された。

class ios_base {
       public:
         typedef T1 io_state;
         typedef T2 open_mode;
         typedef T3 seek_dir;
         typedef implementation-defined streamoff;
         typedef implementation-defined streampos;
       };

basic_streambufからは、stosscが削除された。これは以下のように実装できる。

       template<class charT, class traits = char_traits<charT> >
       class basic_streambuf {
       public:
         void stossc() { sbumpc() ; }
       };

その効果は、ストリームの場所を一つすすめるというものだ。

他にも、実装が自明すぎて意味のないメンバー、重複するメンバーがいくつか削除された。たとえば、basic_iosからはclear。basic_streambufからはpubseekoff, pubseekpos。basic_filebuf/basic_ifstream/basic_ofstreamからはchar const *を引数に取るopenが削除された。

N3654: "quoted" proposal

iostreamにquotedマニピュレーターが入った。サンプルコードを引用すると以下の通り。

std::stringstream ss;
std::string original = "foolish me";
std::string round_trip;

ss << original;
ss >> round_trip;

std::cout << original;   // outputs: foolish me
std::cout << round_trip; // outputs: foolish

assert(original == round_trip); // assert will fire

iostreamでは空白文字で入力が区切られてしまうので、空白を含む文字列を入力した良い場合に問題になる。これに対処するために、デリミタ文字にはバックスラッシュを付与して出力し、入力にあたってはバックスラッシュを取り除く処理をする、quoted manipulatorを追加する。これにより、以下のようにかける。

std::stringstream ss;
std::string original = "foolish me";
std::string round_trip;

ss << quoted(original);
ss >> quoted(round_trip);

std::cout << original;     // outputs: foolish me
std::cout << round_trip;   // outputs: foolish me

assert(original == round_trip); // assert will not fire

ドワンゴ広告

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

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

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

公益財団法人日本生産性本部のヨーゼフ・ゲッベルスが平成28年度の新入社員のタイプは「ドローン型」であるとの報告書を発表した

世界に冠たる優れた人種であるアーリア人の中でもナチ党の国民啓蒙宣伝大臣であるパウル・ヨーゼフ・ゲッベルスは、平成28年度の極東の新入社員(極東における同年に学位を得て同時に雇用契約を結ぶ労働者の謂)は、ドローン型であるとの報告書を発表した。

公益財団法人日本生産性本部の「職業のあり方研究会」(座長 ライズコーポレーション株式会社 代表取締役 岩間 夏樹)は、平成28年度の新入社員の特徴をまとめた。「職業のあり方研究会」は、若年者の就労支援や教育の専門家などで構成され、多くの企業や学校等の就職・採用関係者の協力を得ながら、新入社員の特徴や就職・採用環境の動向などについて調査研究を行っている。

公益財団法人日本生産性本部 - 平成28年度 新入社員のタイプは「ドローン型」

ドローン型というのはよくわからない表現である。座長 ライズコーポレーション株式会社 代表取締役の岩間 夏樹なる人物がまとめたとされているが、報告書を調べてみたところ、実際に文章を執筆したのは別の人物らしい。

報告書はPDFフォーマットで公開されている。

http://activity.jpc-net.jp/detail/lrw/activity001472/attached.pdf

本当の作者名を得るために、そのPDFのinfo情報をみてみると、


$ wget http://activity.jpc-net.jp/detail/lrw/activity001472/attached.pdf
$ pdfinfo attachd.pdf
Title:          プレスリリーステンプレート
Subject:        プレスリリース虎の巻
Author:         Paul Joseph Goebbels
Creator:        Microsoft® Word 2010
Producer:       Microsoft® Word 2010
CreationDate:   Thu Mar 24 10:53:49 2016
ModDate:        Thu Mar 24 10:53:49 2016
Tagged:         yes
UserProperties: no
Suspects:       no
Form:           none
JavaScript:     no
Pages:          3
Encrypted:      no
Page size:      595.32 x 841.92 pts (A4)
Page rot:       0
File size:      814714 bytes
Optimized:      no
PDF version:    1.5

このPDFの作者はパウル・ヨーゼフ・ゲッベルスであることがわかる。奇しくもドイツ、ナチ党の国民啓蒙宣伝大臣と同姓同名である。偶然だろうか。

ヨーゼフ・ゲッベルス - Wikipedia

とはいっても、歴史上有名な方のパウル・ヨーゼフ・ゲッベルスは1945年5月1日に自殺したはずである。仮に生きていたとしても114歳になっている計算だ。

いかにプロパガンダの天才と称されるゲッベルスであっても、極東の事情はわからなかったので、やはりその報告書も精彩を欠くものと見える。

2016-08-15

C++標準化委員会の文書: P0350R0-P0359R0

[PDF] P0350R0: Integrating datapar with parallel algorithms and executors

P0214で提案されているベクトル型、dataparを並列アルゴリズムに対応させて、専用の実行ポリシーを追加する提案。そこまで明示的にする必要があるのは本末転倒な気がする。

[PDF] P0352R0: Smart References through Delegation: An Alternative to N4477's Operator Dot

operator .をオーバーロードすることによりスマートリファレンスを実装可能にしようと言うのがN4477の提案だが、operator .の現在の提案は極めてややこしい。オーバーロード解決のルールにめちゃくちゃ複雑な新ルールを導入するものだ。

この提案では、派生機能を拡張した移譲機能を追加することにより、スマートリファレンスを実装可能にしようと言うものだ。

例えば、以下のように

template < typename T >
class shared_ref : public using T
{
    std::shared_ptr<T> ptr ;
    operator T &() { return *ptr ; }

public :
    // auto x = shared_ref<T>{}のxの型はTになる
    using auto = T ;
    // sizeofの結果はsizeof(T)の結果になる。、
    using sizeof = T ; 

    explicit shared_ref( shared_ptr<T> ptr ) : ptr(ptr)
    { }

    // X::funcをhidingする。
    void func() { }
} ;

このように、既存の派生と継承の上に作ることで、すでによく知られたルールを適用できる。文法はXから派生しているようだが、shared_refのオブジェクトはXのサブオブジェクトを持たない。Xへのリファレンスは、変換関数で取得できるようにしておくことで、shared_refをXとして使いたい場合には、変換関数が使われる。

この拡張によって、スマートリファレンスを実装できるほか、pimplイディオムなども、より自然に実装できる。

これはいい提案だ。operator .のオーバーロードよりはるかに気が利いている。派生と継承のルールはすでによく知られているのでわかりやすい。

入るべきだ。

P0353R0: Unicode Encoding conversions

UTF-8/UTF-16/UTF-32の間の相互変換ライブラリの提案。現在でもC++標準ライブラリで可能ではあるが、極めてクソなライブラリしかない。

using std::literals ;
auto u8str = u8"hello,world"s ;
auto u16str = std::to_u16string( u8str ) ;
auto u32str = std::to_u32string( u8str ) ;
u8str = std::to_u8string( u32str ) ;

UTF-8文字型にはcharではなくて独自の型がほしい。

[PDF] P0354R0: default == is >, default < is < so

P0221で提案されているデフォルトの大小比較演算子に対して、デフォルトの大小比較演算子は有害だと主張する文書。

クラスに対して、デフォルトの==と!=を生成するのはわかる。しかし、<はわからない。多くのクラスは大小比較可能ではない。大小比較がデフォルトで生成されるようになった場合、筆者はコーディング規約でデフォルトでオプトアウトするように支持し、そのためにマクロを使うことも吝かではない。そのような機能はデフォルトで有効にすべきではない。

その上で、文書はデフォルトの大小比較演算子について、以下のいずれかを取るべきだとしている。

  1. 採用しない
  2. デフォルトでオプトインにして、明示的な利用宣言を必要とする
  3. std::orderingのようなカスタマイゼーションポイントを提供して、特殊化することでオプトインにする
  4. 新しい演算子を追加する

文書は、採用しないことが最も望ましく、オプトインもカスタマイゼーションポイントや新しい演算子で行われるべきだと主張している。

P0355R0: Extending to Calendars and Time Zones

にグレゴリオ暦を追加する提案

int main()
{
    using namespace std::chrono_literals ;

    auto date = 2016y/8/10 ;
    std::cout << date ;
}

まあ、ある程度便利だ。日付、曜日、タイムゾーン、うるう秒などに対応している。

P0356R0: Simplified partial function application

std::bindに変わる単純なbindの提案。

bind_frontとbind_backは、関数オブジェクトfと、任意個の実引数を取り、関数オブジェクトを呼び出す際に、実引数の先頭か末尾に受け取った引数を付け加える。

auto front = std::bind_front( f, a, b, c ) ;
front( d, e, f ) ; // f( a,b,c,d,e,f )

auto back = std::bind_back( f, a, b, c )
back( d, e, f ) ; // f( d,e,f,a,b,c) 

std::bindと違い、引数の順序変更や、引数の無視はできないが、この機能で実需要のほとんどは満たせるとしている。

個人的には、lambda式があるのでbind自体がいらないのではないかと思う。

P0357R0: 'reference_wrapper' for incomplete types

reference_wrapperを不完全型に対して使用可能にする提案。

P0358R0: Fixes for 'not_fn'

C++17に入るnot_fnの文面に問題があり、ref-qualifierを無視してしまうので、その修正をした新しい文面案の提案。

[PDF] P0359R0: SG5: Transactional Memory (TM) Meeting Minutes 2016/02/22-2016/05/23

SG5、トランザクショナルメモリーの会議の議事録

ドワンゴ広告

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

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

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

2016-08-09

C++標準化委員会の文書: P0340R0-P0349R0

P0340R0: Making std::underlying_type SFINAE-friendly

underlying_typeに対して非enum型を渡した時にクラスが定義されるように変更することで、SFINAEフレンドリーにする提案。

入るべきだ。

P0341R0: Leveraging parameter packs outside of templates

この提案は素晴らしい。

テンプレートパラメーターパックは素晴らしい。しかし、その利用はテンプレートに制限されている。実にもったいないことだ。パラメーターパックはもっと使われるべきだ。

そもそもパラメーターパックを、そのまま変数にしたい。

template < typename ... T >
struct X
{
    T ... t ;// 認められるべきだ。
} ;

template < typename ... T >
void f( T ... a )
{
    T ... fa(f(a)) ; // 認められるべきだ
}

パラメーターパックはテンプレートの外からでもアクセスできるべきだ。例えばi番目パラメーターパックの値を得るために、以下のようなコードが書けるべきだ。

template<typename ...T>
struct easy_tuple {
 template<typename U> easy_tuple(U &&u) : t{u}... {}
 T ...t;  // パラメーターパックをそれぞれデータメンバーとして持つ
};

template <int i, typename ...T> auto ith_argument(T && ...t) { /*i番目のパラメーターパックを返すコード*/ }
 
template<typename int i; typename ...T> auto get(easy_tuple<T...> tup) {
 return ith_argument<i>(tup....t& ...);  クラスの外側からパラメーターパックのデータメンバーアクセス
}

さて、これはほんの基本だ。本題は、パラメーターパックはテンプレートの外でも扱えるようになるべきだ。そのために、パックリテラルを追加する。

template<typename...T = <double, double> >
struct euclidean_space { /* ... */ };

パックリテラルがあるからには、パック値が存在する。例えば、以下のようなコードは、現在ill-formedである。

auto f()
{
    return { 2, "foo"} ;
}

{}で囲まれた式は何らかのリテラルのように見えるが、現在はそのような扱いを受けていない。パックリテラルが導入されれば、braced-init-listは第一級式として扱えるようになる。すなわち、型は<int, char const *>というパックとなる。z

パックを返すのは、pairやtupleで包んで返すより自然だ。

<double, double> calculateTargetCoordinates();
double distanceFromMe(double x, double y);
 
void launch() {
 if(distanceFromMe(calculateTargetCoordinates()...))
  getOuttaHere();
}

名前付きパックリテラル

<string topSong, person president, double avgTemp> someFactsAboutYear(int year) {
  if(year==1962)
    return {"Stranger On The Shore", Presidents.get("Kennedy"), 14};
}

パックリテラルはtupleよりも直感的な型リストを実現できる。

template<typename L, typename R> struct append;
 
template<typename ...Ls, typename ...Rs>
struct append< <Ls...>, <Rs...> > {
 using type = <Ls..., Rs...>;
};

パラメーターパックは型だけではなく非型も入れられるので、値リストも作れる。

template<typename theoretical, typename actual> struct accuracy;

template<double... predictions, double... measurements> 
struct accuracy< <predictions...>, <measurements...> >) { /* ... */ };

この提案は素晴らしい。パラメーターパックが第一級市民になる。夢が広がる。ぜひとも入るべきだ。

P0342R0: Timing barriers

パフォーマンスの計測は高速なコードを書くために重要だ。パフォーマンスを計測するには処理の前後で時間を取得して差分を得る。

int main()
{
    auto start = std::chrono::high_resolution_clock::now() ;
    do_something() ;
    auto end = std::chrono::high_resolution_clock::now() ;

    auto elasped = end - start ;
    auto millisec = std::chrono::duration_cast<std::chrono::milliseconds>( elasped ) ;

    std::cout << millisec.count() << std::endl ;
}

問題は、C++実装は規格上、as-ifルールにしたがって、処理をリオーダーできるということだ。そのため、C++実装はdo_something関数の呼び出しをstartとendの間からどこか別の場所に移しても、規格上全く問題ない。

このas-ifルールはとても強力で、現在、C++規格には移植性のある方法であるコード辺の処理時間の計測をする方法がない。mutexやスレッドの使用はリオーダーを妨げないし、分割コンパイルですら、プログラム全体の最適化やリンク時最適化の前では通用しない。

そこで、この文書では、std::timing_fenceというコードをそのブロックを超えてリオーダーしないフェンスを追加することを提案している。

確かに入るべきだ。

[PDF] P0343R0: Meta-programming High-Order Functions

メタプログラミングのためのライブラリを追加する提案。MetaとBoost.MPLとboost.Hanaを参考に設計されている。

meta::id

引数をそのまま返すメタ関数。いわゆるidentity。

meta::eval

typename T::typeをしてくれるメタ関数

Meta-Callables型

メタ呼び出し可能型は、ネストされたテンプレートエイリアスinvokeを持つ型である。Booste.MPLならばapplyに相当する。

struct identity
 {
 template <class T>
 using invoke = T;
 };

meta::invoke

meta-callablesのinvokeを呼び出すメタ関数

その他、様々なメタプログラミングを助けるためのライブラリがある。

P0345R0: Allowing any unsigned integral type as parameter type for literal operators

ユーザー定義リテラルの仮引数の型に任意のunsigned integer型を許可する。また、縮小変換を禁止する。

std::uint8_t operator "" _foo ( unsigned long long int x ) { return x ; }

// 縮小変換がかかるが、ユーザー定義リテラルの定義を見なければ縮小変換が起こることがわからない。
auto a = 1024_foo ;
// 縮小変換がかかることが明らかなので警告できる。
std::uint8_t b = 1024 ;

[PDF] P0346R0: A <random> Nomenclature Tweak

Uniform Random Number Generatorは誤解しやすい用語なのでUniform Random Bit Generatorに改名する。

P0347R0: P0347R0 Simplifying simple uses of <random> u

乱数ライブラリを使いやすくするラッパー、random_genrator<T>の提案

現在の乱数ライブラリは初心者には使いにくい。まずUniform Random Number Generatorのオブジェクトを作り、seedを初期化して、望みのdistributionクラスのオブジェクトを作り、URNGとdistributionの2つのオブジェクトを組み合わせて乱数を作る。

提案されているライブラリは、以下のように使える。

#include <random>
#include <iostream>
int main()
{
    std::mt19937_rng rng; // nondeterministically seeded, convenience typedef for std::random_generator<std::mt19937>
    std::cout << "Greetings from Office #" << rng.uniform(1,17) // uniform integer in [1, 17]
              << " (where we think PI = "  << rng.uniform(3.1,3.2) << ")\n\n" // uniform real in [3.1, 3.2)
              << "We " << rng.pick({"welcome",
                                    "look forward to synergizing with",
                                    "will resist",
                                    "are apathetic towards"})
                       << " our management overlords\n\n";

    std::cout << "On the 'business intelligence' test, we scored "
              << rng.variate(std::normal_distribution<>(70.0, 10.0))
              << "%\n";
}

mt19937_rngは、random_generator<mt19937>へのtypedefである。random_generatorは、テンプレート実引数て指定されたURNGのオブジェクトを作り、非決定的な方法でseedする。メンバー関数のuniform(a, b)に整数か浮動小数点数を渡すと、aからbまでの範囲の乱数が生成される。pickを使うと引数に渡したコンテナーの中の要素から一つが選ばれて返される。variateは非一様分布を渡すことができる。

同等のコードをrandom_generatorを使わずに書くと、以下のようになる。

#include <random>
#include <iostream>
int main()
{
    std::random_device rd; // assume unsigned int is 32 bits
    std::seed_seq sseq {rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()};
    std::mt19937 engine(sseq); // seeded with 256 bits of entropy from random_device

    auto strings = {"welcome",
                    "look forward to synergizing with",
                    "will resist",
                    "are apathetic towards"
                   };

    std::cout << "Greetings from Office #" << std::uniform_int_distribution<>(1,17)(engine) // uniform integer in [1, 17]
              << " (where we think PI = "  << std::uniform_real_distribution<>(3.1, 3.2)(engine) << ")\n\n" // uniform real in [3.1, 3.2)
            << "We " << *(strings.begin() + std::uniform_int_distribution<>(0, std::size(strings) - 1)(engine))
                       << " our management overlords\n\n";

    std::cout << "On the 'business intelligence' test, we scored "
              << std::normal_distribution<>(70.0, 10.0)(engine)
              << "%\n";
}

ただ、このライブラリは、uniformメンバー関数を呼び出すたびに対応するdistributionクラスのオブジェクトが作られるので、効率が悪い。このライブラリは効率と引き換えに手軽さを提供するライブラリなのでこれでいいのだとしている。

P0348R0: Validity testing issues

immediate contextによるエラーはハードエラーではないというのがSFINAEの基本となっている。では、immediate contextとは何かという問題が残っている。

この文書は、現在解釈が揺れている例をいくつか上げて、委員会はこの例に対する回答を示すべきであるとしている。

deleted定義を宣言以外の方法で参照するとill-formedであると規定されているが、別の場所では、ハードエラーにはならないと規定されている。どちらが優先されるべきなのか。ハードエラーにはならないほうがSFINAEによる利用ができて好ましい。

GCCもClangも、変数テンプレートの初期化子はimmediate contextにはならないと解釈している。

関数のデフォルト実引数の中の式はimmediate contextかどうかClangとMSVCは一貫してimmediate contextではないとするが、GCCは一貫していない。

他にもまだ問題提起はある。

[PDF] P0349R0: Assumptions about the size of datapar

P0214でdataparというベクトル型の提案をしているが、size()がコンパイル時定数になっている。しかし、世の中には可変長ベクトル型をサポートしたアーキテクチャもある。ベクトル超を可変長にするのは、将来性も見込まれている。size()をコンパイル時定数にする設計は将来の足かせになると主張する文書

ドワンゴ広告

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

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

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

2016-08-08

お気持ち表

国民、某、言さく。国民聞く、かけまくもかしこき天皇、皇位の維持につき、お気持ちを御公開遊ばされると云々。これ日本国の一大事にして、一都一道二府四十三県の貴きと賤しきとを問わず、日本国民の耳目を集むるところなり。

そもそも、天皇は日本国の象徴なり。その地位は、主権の存する日本国民の総意に基くと日本国憲法、かく言へり。天皇は国事行為のみを遊ばされ、国政に関与すること能はざるなり。これをもつてこれをみるに、天皇の皇位の進退、御意に如くに遊ばさるること能はず。就中、真の天皇の御意、公にせられること久しくなし。世にすでに発せられたる詔は、国民の総意なり。御意に非ざるなり。天皇、国政への関与を禁ぜられたるため、僅かにも国政に影響を及ぼす御意は、公にせられざるなり。

昭和天皇、千九百四十六年の一月一日に詔を発して曰く、天皇は現人神に非ずと。これをもって世は天皇は人間なりと宣言したとみなせり。畏くも天皇たりといへども、一個の人間たることはこれ明らかなり。

而して、天皇は日本国民なりや。国民某、これを知る能はざるなり。ただし、密かに思ふよう、天皇は日本国民にあらざるなり。日本国憲法より天皇は、

  1. 天皇の地位は日本国民の相違に基づく
  2. 天皇は国政に関する権能を有しない

と定義されたり。一方、日本国民は、

  • 日本国民は基本的人権を持つ
  • 日本国民は法の下に平等である。華族その他の貴族の制度は、これを認めない。
  • 日本国民はいかなる奴隷的拘束も受けない

今、試みに天皇を日本国民とす。日本国民は国政に関する機能を有する。これは天皇の定義と矛盾する。他の天皇の定義と日本国民の定義もそれぞれ矛盾するが自明のため省略。ゆえに天皇は日本国民に非ざるなり。

国民某、思ふよう、たとひ天皇は国政への参加を否定されたりといへども、一個の人間なり。人間はお気持ちを公開して可なり。お気持ちの公開は国政への関与にあらざるなり。お気持ちを国政に反映させるや否やの判断は日本国民に委ねられたり。

伏しておもんみるに、畏くもかたじけなき天皇のお気持ちを正しく流通せられること、これ重要なり。世にはびこる悪鬼外道、天皇のお気持ちをいささかの敬いの心もなくして改変すること、これ恐れあり。

ラジオは、お気持ちの流通手段として不適なり。何となれば、ラジオの発信者、お気持ちを改変する恐れこれあり。電波はいかに賎しの山賤といえども任意の周波数の発信が容易なり。

テレビは、お気持ちの流通手段として不適なり。何となれば、テレビは技術的に劣つたインターレース、技術的に劣つている上に特許汚染されたMPEG2、特許汚染されたAACを使ひ、かつ設計上の欠陥にして人道上の罪なるDRMを用ひたればなり。

天皇はお気持ちをデジタルデータに符号化するにあたって、よろしく自由なる動画符号化形式、自由なる音声符号化形式、UTF-8で符号化されたるプレインテキストを用い遊ばされたまえ。

流通は、天皇のお手元から末端の取得者までの経路がTLSで暗号化されていることが肝要なり。しかし、これとても認証局を信用せずんばあらざるなり。

幸いにして、国民某、改変を検知する算の法を知りたり。算の法の妙によりて、流通経路に不信ありとも改変の有無を検知できる神秘の法なり。

ハッシュ値は、多大なビット列を元に計算した値なり。ハッシュ値を表現するには数百ビットもあれば足る。セキュアなハッシュ関数を選択することこれ肝要なり。CRC、MD1, SHA1のごときは、既知の脆弱性があるため御避け遊ばされたまえ。天皇はよろしくSHA-2かSHA-3を御使い遊ばされるべきなり。

公開鍵暗号による電子署名は、事前に公開鍵さえ信頼できる方法で流通すれば、お気持ち発信者の秘密鍵による署名を、公開鍵所有者が検証し、お気持ちに改変がないことを検知できるものなり。天皇、よろしくGPGにて御公開鍵を御生成の上、御公開遊ばされたまえ。

国民某、思ふよう。天皇、思慮知恵、常の人にすぐと言へども、お気持ちの過ちすることこれ恐れあり。誤りは修正さるべきなり。かるがゆえに、天皇、よろしく御自らgitをお使い遊ばされて、お気持ちをバージョン管理遊ばされたまえ。

次に天皇、よろしく御自らGitHubの御アカウントを御取り遊ばされ、お気持ちレポジトリに御git push遊ばされたまえ。これにて、日本国民、もし、かたじけなくも天皇のお気持ちに誤字脱字などの誤りを発見した時は、恐れながらローカルにてお気持ちを編集させていただき、畏くもforkしたお気持ちレポジトリにgit pushしたうえで、恐れながらPull Requestなど発行させていただき、而して、天皇、かたじけなくも御自らPull Requestを叡覧あって、御マージ遊ばされたまえ。

国民某、伏して惟んみるに、天皇、御多忙につき、ハッシュ値、電子署名、中間者を介することなく配布、途中の経路をTLSで暗号化を、御自ら行い遊ばされることは煩わしからん。国民某、つらつら既存のソフトウェアを眺めるに、右のこと、電子署名以外ならば行うソフトウェアこれあり。BitTorrentプロトコルとそのクライアント、すなわちこれなり。天皇御自ら流通、ハッシュ値計算、途中の経路はTLS暗号化までは自動で行うソフトウェアなり。

国民某、また思わく、天皇、御身分を御隠し遊ばされたまま、お気持ちを御公開遊ばされたきこと、定めてこれありなん。世に、Torとて多重中継を経て匿名性を高めるソフトウェアこれあり。よろしくお使い遊ばされたまえ。

謹んで表を奉り以聞す。国民某、誠惶誠恐頓首頓首死罪死罪。

2016-08-06

アウティングは違法か?

以下のようなニュースがある。

「ゲイだ」とばらされ苦悩の末の死 学生遺族が一橋大と同級生を提訴

内容を要約するとこうだ。

同性愛者の男Aは、厳格な異性愛者の友人Bに好意を打ち明けた。Bは否定した。Bは周囲に、Aは同性愛者であることを暴露した。Aは心療内科に通院するほどの精神的苦痛を受けた。Aが籍をおく大学の相談室は、Aの相談に対して、同性愛問題を正しく把握せず、見当違いの性同一性障害を専門とする病院を紹介した。この対応にAは精神的苦痛を受けた。Aは精神的苦痛を理由として自殺した。

Aの遺族はBを訴えた。理由は、Bが周囲にAが同性愛者であることを暴露したことによる精神的苦痛は損害賠償を請求するに足るからだ。

Aの遺族は大学を訴えた。理由は、「同性愛者、うつ病、パニック発作についての知識・理解が全くなく、模擬裁判の欠席は前例がない、卒業できないかもしれない、などとプレッシャーをかけた」ことによる精神的苦痛は損害賠償を請求するに足るからだ。

Bは、「恋愛感情をうち明けられて困惑した側として、アウティングするしか逃れる方法はなく、正当な行為だった」と主張した。

大学は、「大学の対応に問題はなかった。個別の事故は防げない」と主張した。

この裁判は興味深いので、有名な判例となるはずだ。この記事では、アウティング(同性愛者であることを周囲に暴露すること)の是非について論じているが、この件は同性愛者とは切り離して考えるべきだ。というのも、

「ホモではない男がホモの男から言い寄られたので拒否したうえで、気味悪く感じ周囲に吹聴した。ホモは自殺した。」

と書けば、なるほど、ホモフォビア(ホモ恐怖症)の男による理解のない行動が悩める男を自殺に追い込んだ。悪だ。と思えるかもしれない。しかし、同性愛者という文脈から切り離すと、

「人Aは人Bから言い寄られたので拒否したうえで、気味悪く感じ周囲に吹聴した。Aは自殺した。」

となる。では、ここに別の文脈をつけてみよう。

「20代の若くて美人な女子大生が、40代でハゲでデブでキモいオッサンに言い寄られたので拒否したうえで、気味悪く感じ周囲に吹聴した。オッサンは自殺した。」

これはどうだろうか。当然の結果だろうか。それとも、ルッキズム(Lookism、人を見た目の良し悪しで判断する美貌主義)とジェロントフォビア(Gerontophobia、加齢恐怖症)の女による理解のない行動が悩める男を自殺に追い込んだ。悪だ。と思うだろうか。

文脈を取っ払うと、どちらもやっていることは同じだ。そして、現実を見ると、同性愛者の男性カップルと同じ程度には、40代の男と20代の女の年齢差カップルは存在するのではないか。もしそうであるならば、後者と前者は同じ程度に起こりうる問題であり、当然同じ判断が適用されるべきである。残念ながら、具体的な統計が見つからないので、カップルの比率は感覚でしかないのだが。

言い寄られた立場から考えると、どちらも言い寄られたことに対して不快感を持ったとしてもおかしくはない。厳格な異性愛者が同性愛者から言い寄られたら不快感を持つであろうし、20代の若い女性が40代の性的魅力のない男から言い寄られるのも不快感を持つだろう。その不快感の解消方法として、周囲に相談をすることは悪であろうか。

と考えていくと、私はこの問題の善悪を判断できかねる。納得の行く説明付きの判決が出て欲しいのだが、おそらくどちらに転ぼうとも私の常識では納得できないだろう。

大学に関しては、私は責任がないと考える。というのも、私は「無知が罪」となることはおかしいと考えているからだ。大学の相談室に同性愛に対する高度な医療知識がなかったために、間違った診療科を紹介したことは罪ではない。また、クラス替えや留学の希望に対する対応でも、同性愛が絡まない他の事例と代わりがないのであれば、特別に差別されていたとは言えない。それに、大学は学生の私的な生活に踏み入るべきではない。

Bjarne Stroustrupのプログラミング入門書の査読の感想

アスキードワンゴ編集部からBjarne StroustrupのProgramming -- Principles and Practice Using C++という本の第二版の邦訳が出版される。初版は翔泳社が出していたが、C++14に対応した改訂版の第二版の版権が空いていたので、アスキードワンゴから出すための作業をしていた。私は邦訳の査読をした。

今年になってから半年は、ずっとこの本の査読をしていた。このためにC++標準化委員会の最新の文書を把握する作業が数ヶ月ほど滞った。そして、この仕事は、私がドワンゴに入社して以来、最悪の仕事であった。ただし学びはあった。それについて書いていこうと思う。

Bjarne Stroustrupは、ご存知の通りC++の最初の設計者にして最初の実装者である。現在、Texas A&M Universityで教鞭をとっている。この本はStroustrupの講義のための教科書として書かれた。対象読者は、入学して最初のセメスターで初めてプログラミングを学ぶ学生である。

結論から言うと、この本は極めて悪く書かれている。およそ悪書の見本のような本だ。悪文の集大成といってもよい。プログラミング言語入門用としても悪い。

先に、この本の査読の仕事は、私がドワンゴに入って以来最悪の仕事だと書いたのは、この本が極めて読みづらく不必要に難解で、しかもその内容が噴飯物の間違いだらけであったからだ。学びがあったというのは、この悪書の見本を網羅している本を査読することで、「いかに本を書いてはいけないか」について実感できたからだ。かのLinus Torvaldsは言った(どこかで読んだ記憶があるのだが、出典が見当たらない)

「迷いが生じた時は、Subversionの逆をやることにした」

-- Linus Torvalds、gitを開発することにおいて

今回、この本の査読という貴重な体験ができたことで、今後私がプログラミングの参考書を執筆するときは、反面教師として逆張りを行うようにする。

さて、ここからは、Stroustrupのプログラミング入門がいかに悪書であるかを、具体的に書いていく。

本の文章量が多すぎる。

Stroustrupのプログラミング入門第二版の原書は1312ページある。邦訳も同じ程度のページ数がある。本書の担当の編集者は、「僕が担当した本のなかでも最大の記録を更新しましたね」と言った。私はこの本を鈍器と呼んでいる。理由は、人が殴り殺せるほどの重さがあるからだ。

これが、Stroustrupのプログラミング言語C++のような本であれば、どれだけ文章量が多かろうと問題はない。そういう本だからだ。しかし、これは初心者への入門書である。

本書の翻訳は、第一版と同じ翻訳者が新たに行った。翻訳の質は、概ね原書に忠実だ。一部変な翻訳を発見したし、まだ未発見の問題もあるだろうが、これだけの文章を翻訳したにしては、かなり一貫している翻訳だ。

問題は翻訳ではなく原文(英語)にあるというのも原文は極めて読みづらく関係詞(thatとかwhichとか)や接続詞(andとかorとかyetとか)で文章を遠慮無く一文一文を長大にしている上に括弧の多用(こういうやつ)で更に文章の長さを水増ししているからであって決して翻訳の質が低いからではなく概ね原文に責任がある。

文章はしばしば話が脱線する。「Aをするのは良い作法であるとされている」、「Bは悪い作法であるとされている」などの妥当ではあるが冗長な話への脱線がよくある。

そして中でもひどいのは、わざと間違ったソースコードを提示したうえで、「これが私たちの考えた最初の解法だ。しかしこれは動かない。なぜだろうか考えてみよう。一見すると動くように見える。何が間違っているのだろうか。ひょっとしてAだろうか。いや違う。Bだろうか。そうでもない。実は・・・」といった自問自答の文章が延々と続くものだ。

この本は、筆者と読者をひっくるめて「私たち」と表現している。これはこの本の独特の表記法であり、わざわざ、「なぜ私たちは私たちという表記を採用するに至ったか」という説明まで書いてある。

xkcd: Manuals

そこまで書いている以上、翻訳でも「私たち」という表現を維持しなければならないのは当然の話だ。実際維持しているが、極めて日本語として不自然な文章になっている。

このことから、私たちはとても重大な教訓を得た。

教訓1: 文章は簡潔にすべし

人間が読める文の量と速度は有限である。重要なのはプログラミングの仕方を教育することであって、文章の量ではない。

これは文が悪文だったというものだ。次は技術的な悪書の理由について書く。

サンプルコードの1割ほどがコンパイルできない

コンパイルできない理由は、識別子が間違っているとか、あるべき記号が欠けているとか、極めて些細でお粗末なtypoがほとんどだ。

このような問題は、本のソースコードからサンプルコードを抜き出してコンパイラーに通して文法違反がないかどうか確かめるテストを書けば防げる話だ。そして、この本はわざわざ大量の文章を割いて、テストの重要性を説いている。Stroustrupには、「医者の不養生」というニッポンのコトワザを教えてあげたい。

この本は、はじめC++11向けに、おそらく2006年か2007年ごろから書かれたものが、2008年に出版され、それをC++14に対応させ、また C++11の規格制定後に、C++11の新機能を使った行儀の良いお作法に対応させる修正を行っている。

その過程で、「ふふん、この程度の変更は自明だからコンパイルして確認するまでもないね」という怠惰とうぬぼれがあったのだろう。テストを書くべきである。

Stroustrupであろうと混乱したであろう興味深い間違いの例はある。例えば、変数の初期化をbrace-or-equal-initializerにすることでnarrowing conversionを防ぐなどのお作法のために、サンプルコードを全面的に修正したりしている。

void f( int x )
{
    short s1 = x ; // OK、ただし縮小変換
    short s2{x} ; // エラー、縮小変換は禁止されている
} 

問題は、これを以下のようなstd::string s(1, 'a')というコードに対して盲目的に適用してしまったことだ。

// "a"で初期化
std::string s1(1, 'a') ;
// "\x01a"で初期化
std::string s1{1, 'a'} ;

理由は、brace-or-equal-initializerでは、まずstd::initializer_list<T>を引数に取るコンストラクターが探され、ない場合は、その他のコンストラクターで引数の数と型があるものが探される。std::stringには、initializer_list<char>を引数に取るコンストラクターがあるので、そちらが優先される。

そして、リスト初期化のnarrowing conversionの禁止は、コンパイル時定数で変換先の表現可能な範囲である場合は発生しない(より正確には、そのような場合はnarrowing conversionとみなされない)。その結果、{'\x01', 'a'}というリスト初期化だと解釈されてしまった。

これはコンパイルも正常に通ってしまうので、実行して出力を確認しない限り見抜けないバグだ。

もうひとつの例は、C++03とC++11で挙動が変わるコードだ。

bool can_open(const string& s)
// check if a file named s exists and can be opened for reading
{
     ifstream ff(s);
     return ff;
}

can_open関数は、引数として与えられたファイル名でファイルを開けるかどうかを確認する関数だ。問題は、ffがファイルを開いているかどうかを、boolへの暗黙の型変換で調べている。ifstreamはbasic_iosから派生していて、basic_iosはoperator void *()という変換関数を持っていて、ファイルが開いているかどうかをnullptrかどうかで返すのだが、これは問題が多いとして、library issue 468ではexplicit operator bool()に変更された。そのため、C++11ではコンパイルエラーになる。

そもそも、暗黙の型変換に頼るのが間違いなのだ。ファイルを開いているかどうか確認するメンバー関数is_open()があるのだから、それを使えばよい。

これは、コンパイルさえしていれば発見できた問題だ。

全体的には、極めてお粗末で些細なill-formedなサンプルコードばかりだ。

教訓2: テストは重要だ

著者の文字コードへの理解がない

本にはこう書いてある。

「テキストファイルの最初の4バイトは4文字である」

この文脈は、テキストファイル一般の話だ。これから扱う課題の入力に使うテキストファイルの最初の4バイトは常に4文字であるとみなすという仮定の話ではない。つまり、1文字は1バイトであると書いていることになる。

このような内容のプログラミングの参考書を日本で出版したならば、マサカリが雨となって降り注ぎ、著者、編集者、査読者は二度と顔を出して表を歩けず、出版社の信頼は地に落ちることうけ合いだ。そして、すでにC++11にはUTF-8/UTF-16/UTF-32文字列リテラルが入っているのだからなおさら悪い。この文章は到底受け入れがたい。

そして、この本には、「中国の文字やマラヤーラム文字をサポートするには」などという文章も入っている。中国の文字やマラヤーラム文字をサポートした現実の文字コードは、すべて可変長エンコードであり、1文字は1バイトとは限らない。

また、中国の文字やマラヤーラム文字をサポートするには、というのに続けて、C++の標準ライブラリのiostreamやlocaleの詳細な参考書を読むべきだとも書いている。iostreamやlocaleは、設計上可変長エンコードに対応できず、この文書は無責任である。

またひどいことに、C++11で入った正規表現ライブラリもUnicodeに対応していると書いている。実際は対応していない。そもそも、可変上文字列に対応できない設計になっている。受け入れがたく無責任にもほどがある。

筆者はこれを何度もBjarne Stroustrupに指摘したが、残念ながら話が噛み合わず、本人に修正を促すことはできなかった。曰く、「これは英語圏の話であるから」。しかし、もはや英語圏とてUnicodeから逃れることはできないというのに。

そして、筆者の使う、unacceptableとかirresponsibleという言葉を侮辱的であると返すばかり。侮辱的にしろ、unacceptableなものはunacceptableであり、irresponsibleなものはirresponsibleでしかない。

このため、該当箇所に間違っていると指摘する注釈を入れ、また原文にない章を特別に追加して、現実の文字コードと、C++の文字と文字列とライブラリ、C++の規格と現実の文字コードの対応、Apple, GNU/Linux, Windowsにおける文字コードの取り扱いなど、日本人プログラマーなら誰でも知っているし、Wikipediaなどで簡単に調べられることを、簡単に浅く説明した。筆者の専門ではない分野を扱ったのでマサカリが山と飛んでくることを恐れている。

Cプリプロセッサー

この本のサンプルコードは、すべてstd_lib_facilities.hというヘッダーファイルを#includeすることが前提で書かれている。このヘッダーの中身は、本で使っている標準ライブラリヘッダーの#includeや、有益ないくつかの関数の追加。またusing namespace std ;などが書かれている。グローバル名前空間に読み込まれるヘッダーファイルでusing namespace std;を書くのはよろしくないが、これが入門書だということを考えると、まだ納得もできる。

ただし、納得のできないことはある。これだ。

#define vector Vector

Cプリプロセッサーで、vectorというトークンをVectorに置き換えている。Vectorとは何か。以下のように定義されている。

template< class T> struct Vector : public std::vector<T> {
 using size_type = typename std::vector<T>::size_type;


 using std::vector<T>::vector; // inheriting constructor

 T& operator[](unsigned int i) // rather than return at(i);
 {
  if (i<0||this->size()<=i) throw Range_error(i);
  return std::vector<T>::operator[](i);
 }
 const T& operator[](unsigned int i) const
 {
  if (i<0||this->size()<=i) throw Range_error(i);
  return std::vector<T>::operator[](i);
 }
};

ようするに、operator[]でもat()を使った範囲外チェックを行うようにするものだ。

本では、これを必要悪であり、現実のソフトウェアプロジェクトでもこのような技法を使うことはあり、実際に役に立っていると書いているが、とんでもないことだ。

範囲外チェックを行いたければ、最初からat()を使っておけばいいのだ。fstreamを暗黙の型変換でboolに変換してファイルがオープンされているかどうか調べようとしたり、Stroustrupは大昔の粗野な時代の悪い癖が抜けていないのではないかと疑う。

課題の設定

この本では、まず冒頭で変数や関数、式、文など言語の文法の基本を教えたあと、次の章で練習課題を出している。この本は、まだプログラミング経験の一切ない初心者を対象にしていることを思い出してほしい。その上で、いま変数や関数やif文を覚えたばかりの初心者が、以下の課題を解く難易度を考えてほしい。

「実数と四則演算と括弧がある数式を標準入力から読み込んで計算結果を標準出力する計算機を実装せよ」

実数と四則演算までならまだ初心者でもなんとかなるだろうが、括弧が出てきた時点で完全に初心者向けではない。

この本では、まずBNF記法で数式の文法を定義したあと、教科書的な再帰下降構文解析による数式のパーサーを書いて計算を行っている。SICPでも冒頭でここまで無茶な課題はなかったはずだ。

もちろん、面白い課題であり、全プログラマーがプログラミング学習の比較的早い段階で一度は練習のために実装してみるべき課題ではあるが、いかにもタイミングが早すぎる。

Bjarne Stroustrupの本がここまで悪書だとは思わなかった。特に文字コードへの理解が致命的にない。

Stroustrupのプログラミング入門の査読の仕事で、筆者は反面教師としての貴重な教訓を得た。また、C++11/14コア言語の執筆の経験からも、いろいろと執筆環境の不満点はあった。そこで、筆者が次に参考書を執筆するときは、以下のような環境で行おうと考えている。

  • OSにUbuntu GNU/Linuxを使う。理由は、必要なソフトウェアが標準のパッケージマネージャーで揃う上、C++を書く上で圧倒的に快適だからだ。
  • テキストディターにVimを使う。
  • 執筆環境はgitで管理する。

  • GNU Makeで参考書のビルドとテストを管理する。これ以上に複雑なビルドシステムは、参考書執筆では必要がないと判断した。
  • 参考書のソースコードはMarkdownで書く。
  • HTMLなどの各種フォーマットへのコンパイルはpandocを使う。
  • HTMLへの出力では、数式はmathjaxを使う。ただし、KaTexも興味深いので評価中だ。
  • HTMLへの出力では、ソースコードはhighlight.jsを使ってシンタックスハイライトする。
  • 参考書のソースコードからサンプルコードを抽出してGCCとClangで文法チェックをする。
  • textlintで日本語のチェックをする。できるだけ簡潔な文章を維持するために文章にこういったツールで制約をかける。

すでに、これらのことをだいたい実装している。まだC++17の参考書を書くには不確定要素が多すぎるが、そろそろ準備は始めるべきだ。

一つ不満なのが、textlintの実装がnode.jsで書かれていることだ。もちろん、ツールとして使うのであるから、正しく動作さえすれば、どのような言語で書かれていようと気にしないのだが、結果的に、textlintの導入方法がなかなか厄介だった。Ubuntuにはnodejsパッケージがあるが、これはJavaScriptの実行環境が/usr/bin/nodejsというファイル名になっている。理由はすでにnodeというファイル名のパッケージが存在していて、名前が衝突するからだ。nodeパッケージをいれないのであれば、nodejs-legacyパッケージを入れることによって、/usr/bin/nodeへのsymlinkを貼れる。そして、GitHubのレポジトリから直接/usr/localにファイルを引っ張ってくる。npmにより、パッケージ間の存関係が極めて複雑で、とても些細な機能ですらパッケージ化されている。

ドワンゴ広告

ドワンゴは1文字は1バイトではないとわかっている本物のC++プログラマーを募集しています。

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

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

2016-08-02

C++標準化委員会の文書: P0330R0-P0339R0

[PDF] P0330R0: User-Defined Literals for size_t

std::size_t型を返すuser defined literal suffixのzuを追加する提案。これにより、size_t型の変数をautoで宣言できるようになる。

using namespace std::support_literals;
auto size = 123zu ;

サフィックスのない整数リテラルの型はint型になるため、これまでズボラなプログラマーは符号付き整数をstd::size_t型の変数に代入したり比較したりしていた。符号付き整数と符号なし整数を変換したり比較すると、思いがけぬ挙動につながるので、好ましくない。

P0331R0: P0331r0 : Motivation and Examples for Multidimensional Array

多次元配列array_refの動機と利用例

P0332R0: P0332r0 : Relaxed Incomplete Multidimensional Array Type Declaration

array_refは、配列型を渡すことによって、一部を動的なサイズにすることができる。

array_ref< int [][5][] > a ;

これを実現するために、配列の宣言の文法を変更する。

P0333R0: P0333r0 : Improving Parallel Algorithm Exception Handling

Parallelism TSで並列ベクトル実行ポリシーでアルゴリズムを実行した時に、要素アクセス関数が例外を投げた場合、直ちにstd::terminateが呼ばれる。これは他の実行ポリシーのexception_listを投げる挙動と違い、一貫性がない。そこで、並列ベクトルポリシーの場合でもexception_listを投げるようにする。また、並列ベクトル実行ポリシーはbad_allocも投げることがある。

P0334R0: P0334r0 : Immutable Persistent Containers

変更できないコンテナー、immutable_listの提案。

immutable_listは要素を変更出来ないコンテナーだ。「変更」したいときは、新しいimmutable_listのオブジェクトを作る。要素は参照カウンターで管理されている。

確かに、標準ライブラリにほしいコンテナーだ。Haskellが参考にされている。

[PDF] P0335R0: Context Tokens for Parallel Algorithms

並列アルゴリズムにコンテキストトークンを追加する。

parallel::for_each( vec, 0, N, []( auto i )
    {
        parallel::for_each( par, 0, N, func ) ;
    } ) ;

このように、並列アルゴリズムがネストする場合で、外側の実行ポリシーがベクトルで、内側の実行ポリシーが並列の場合、挙動は未定義になる。この誤りを検出する方法がない。

そこで、要素アクセス関数(この場合、lambda式やfunc)にコンテキストトークンを与え、同トークン経由で並列アルゴリズムを呼び出す方法を付け加える。

parallel::for_each( vec, 0, N, []( auto context, auto i )
    {
        context.for_each( par, 0, N, func ) ;
    } ) ;

これにより、実行ポリシーの伝播が正しく行われるようになる。

[PDF] P0336R1:Better Names for Parallel Execution Policies in C++17

並列アルゴリズムの実行ポリシーの名前をリファクタリングする提案。シングルトンオブジェクトのseq, par, vecは変わらないが、std::execution名前空間の下におかれるようになる。

P0337R0: P0337r0 | Delete operator= for polymorphic_allocator

polymorphic_allocatorからoperator =を削除する提案。

polymorphic_allocatorはステイトフルなアロケーターである。propagate_on_container_copy_assignment と propagate_on_container_move_assignmentはfalseを返す。これは意図的なものである。理由は。ステイトフルなアロケーターは一度コンテナーに入れたら、そこから動かすべきではないからだ。その設計思想から考えれば、そもそもoperator =を提供すべきではない。operator =を使って失敗する例がいくつもある。そこで、operator =を削除する。

[PDF] P0338R0: C++ generic factories

汎用的なfactory関数の提案。

C++には、いわゆるmake系のfactory関数が存在する。例えば、make_sharedとかmake_uniqueとかmake_pairなどだ。

make関数が作られる理由は主に2つある。

新しく型を作るfactory関数

back_inserter、make_optional、make_ready_future, make_expectedなど、新たに型を作り、そのオブジェクトを返すものがある。

emplace構築を行うもの

make_uniqueやmake_sharedなど。

また、make_pairやmake_tupleのような、新たに型を作り、かつemplace構築まで行うものがある。

この提案は、そのような様々なmake_foobar関数を、make関数に統一しようと言う提案だ。例えば以下のようなードが、

int v=0;
auto x1 = make_shared<int>(v);
auto x2 = make_unique<int>(v);
auto x3 = make_optional(v);
auto x4v = make_ready_future();
auto x4 = make_ready_future(v);
auto x5v = make_ready_future().share();
auto x5 = make_ready_future(v).share();
auto x6v = make_expected();
auto x6 = make_expected(v);
auto x7 = make_pair(v, v);
auto x8 = make_tuple(v, v, 1u);
future<int&> x4r = make_ready_future(std::ref(v));
auto x1 = make_shared<A>(v, v);
auto x2 = make_unique<A>(v, v);
auto x3 = make_optional<A>(v,v);
auto x4 = make_ready_future<A>(v,v);
auto x5 = make>(v, v);
auto x6 = make_expected<A>(v, v);

以下のように書ける。

int v=0;
auto x1 = make<shared_ptr>(v);
auto x2 = make<unique_ptr>(v);
auto x3 = make<optional>(v);
auto x4v = make<future>();
auto x4 = make<future>(v);
auto x5v = make<shared_future>();
auto x5 = make<shared_future>(v);
auto x6v = make<expected>();
auto x6 = make<expected>(v);
auto x7 = make<pair>(v, v);
auto x8 = make<tuple>(v, v, 1u);
future<int&> x4r = make<future>(std::ref(v));
auto x1 = make<shared_ptr<A>>(v, v);
auto x2 = make<unique_ptr<A>>(v, v);
auto x3 = make<optional<A>>(v,v);
auto x4 = make<future<A>>(v,v);
auto x5 = make<shared_future<A>>(v, v);
auto x6 = make<expected<A>>(v, v);

make関数にテンプレート名を渡すと、それぞれに対応したfactory関数として振る舞う。

テンプレート名ではなく、型を渡すことも可能である。これによって具体的に指定することも可能になる。

auto x = std::make< unique_ptr<long>>(0) ;

P0091によって、コンストラクターからテンプレート実引数の推定ができるようになるので、make関数の一部の機能は普通に書けるようになるが、それでもmake関数の優位な点はいろいろあると主張している。

customization pointとしては、std::factory_traits<T>が提供されている。これを特殊化することによってユーザー定義のクラスに対する挙動を追加できる。

[PDF] P0339R0: polymorphic_allocator<void> as a vocabulary type

polymorphic_allocator<void>を、型を指定せずに使えるあロケーターにしようと言う提案。

ドワンゴ広告

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

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

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