12月7日の日曜日に、妖怪ハウスでボードゲーム会を行うことにした。
特に時間を設けてはいないので、当日の何時に来てもよい。
今回はピザを焼く予定だ。
妖怪ハウスの住所は、中野区野方5-30-13 ヴィラアテネ401となる。
12月7日の日曜日に、妖怪ハウスでボードゲーム会を行うことにした。
特に時間を設けてはいないので、当日の何時に来てもよい。
今回はピザを焼く予定だ。
妖怪ハウスの住所は、中野区野方5-30-13 ヴィラアテネ401となる。
プリプロセッサーの規格には、未定義な挙動とされている部分があるが、多くの主要な実装で似通った実装になっているところもあるので、その部分を規格で追認する。また、生文字列リテラルやユーザー定義リテラルなど、C++の新しい文法にプリプロセッサー規格が追いついていないので、対応させる。矛盾した点や曖昧点も改善する。
プリプロセッサーの定義に未定義な挙動が多いのは、実装に自由度をもたせたり、独自拡張を許可したりするためだったのだが、結局問題のある未定義の挙動は違法であると定義したほうがよいし、当時は独自拡張のために未定義にしていたところも、数十年たてば、主要な実装はほぼ似たような実装に落ち着いたりした。
寿命修飾子(lifetime qualifier)の提案。
一時オブジェクトの寿命は、完全な式の中である。
ただし、リファレンスに束縛された一時オブジェクトは寿命が延長される。
struct X { } ;
int main()
{
// 一時オブジェクトの寿命が延長される。nn
auto && ref = f() ;
}
しかし、関数呼び出しの結果返されるリファレンスに対しては、たとえ参照先が一時オブジェクトであったとしても、寿命延長が行われない。
struct X
{
int data ;
~X()
{
std::cout << data << ": destructed.\n" ;
}
} ;
int main()
{
auto && r1 = X{1} ; // 寿命延長される
auto && r2 = std::move( X{2} ) ; // 寿命延長されない
std::cout << "end of scope.\n" ;
}
なぜならば、関数の実引数として与える式がfull-expressionだから、その時点でmain関数のブロックスコープのリファレンスに束縛されていない以上、寿命延長されないのだ。関数を呼び出した結果のリファレンスは、たとえ一時オブジェクトを束縛していたとしても、寿命延長されない。
これにより、クラスのアクセッサー(getter)が使えず、データメンバーを直接publicで使わなければならないという問題もある。
struct X
{
int data ;
int & get_data() const { return data ; }
} ;
int main()
{
auto && ref = X{}.data ; // 寿命延長される
auto && ref = X{}.get_data() ; // 寿命延長されない
}
ここで、dataはprivateなメンバーにして、publicがget_dataでアクセスさせたいとしても、寿命延長されないという点で使えない。
これが具体的に問題になるのは、例えばrange-based forだ。
struct has_vec
{
// これはprivateメンバーにしたい
std::vector<int> m = { 1, 2, 3 } ;
std::vector<int> & get_vec() const
{ return m ; }
} ;
int main()
{
for ( int val : has_vec{}.m ) ; // OK
for ( int val : has_vec{}.get_vec() ) ; // エラー、寿命延長されない
}
もっと具体的な例を挙げると、Boost.Adaptorsをrange-based forで使いたい場合がある。
std::vector<int> vec;
for (int val : vec | boost::adaptors::reversed
| boost::adaptors::uniqued )
{
// 一時オブジェクトはすでに破棄されている
}
関数を超えて一時オブジェクトの寿命を延長させるには、新しい言語機能が必要である。
関数の仮引数宣言に寿命修飾子(lifetime qualifier)であるexportキーワードを記述して、寿命を延長させることができる。
struct X { } ;
template < typename T >
T const & f( export T const & t )
{
return t ;
}
int main()
{
auto && ref = f( X{} ) ; // 寿命延長される
}
データメンバーを直接使わなければならない問題も、寿命修飾子により解決できる。
class has_vec
{
private :
std::vector<int> m = { 1, 2, 3 } ;
public :
std::vector<int> & get_vec() export const
{ return m ; }
} ;
int main()
{
for ( int val : has_vec{}.get_vec() ) ; // OK
}
これにより、Boost.Adaptorsの例も動く。
連続したストレージを多次元配列に見せかけるライブラリarray_viewに対する機能追加の提案。
テンプレートで行が先か、列が先か、レイアウトを選べるようになる。
既存の似たようなライブラリからの移行を用意にするため、operator ()をサポート
operator []はレイアウトが行列どちらでも動作が変わらないようにする。
内部的なポインターにアクセスできるようにする。
特に1次元配列の場合だが、RandomAccessContainerとrangeのサポート。
N4074で提案された、return {expr}で暗黙の型変換がおこるときでもexplicitコンストラクターを呼び出せるようにする提案に対する反論。
explicitなものはexplicitなのだから制限を緩和すべきではないとしている。
N4204で提案されている同期ライブラリ、ラッチとバリアーに対する機能追加の提案。
ラッチのオブジェクトを安全に破棄するためには、そのラッチに対してwaitしているすべてのスレッドのブロックを解いてからでなければならない。これは、ラッチをローカル変数として確保している場合、そのスレッドはブロックスコープを抜ける前にwaitしなければならないことを意味する。
これを避けるために、以下の関数でSelf-Destructするラッチを生成できるようにする。
static latch* create_self_deleting( int C );
それから、ラッチの完了通知ができるようになった後、スレッドのブロックが解かれる間の状態のときに、呼び出されるコールバック関数を指定できる、flex_latchを追加する。
基本クラスをクラス定義の中で宣言できる機能の提案。
まだ大雑把な構想段階だが、たとえばこういうコードが書けるようになりたい。
struct Base
{
Base( std::string ) ;
} ;
class Derived
{
members:
string str = "hello" ;
public bases :
Base{ str } ;
} ;
アクセス指定子の文法を拡張することで、基本クラスをクラス定義の中で指定できる。
しかも、基本クラスよりも上に書かれているデータメンバーの初期化の方が、基本クラスの初期化より先に行われるので、基本クラスの初期化に派生クラスのデータメンバーを使うことができる。
論文はとても短く、大雑把な構想しか記述していない。そして最後に、
この提案の目的は何か?
簡単に言えば、この提案は複数の欲しがられている機能を汎用的に提供できる、とても大雑把な構想だ。パーサーだとかそのへんの問題はいろいろあるだろうけど、とりあえず目安として、まず聞きたいことは、
- We hate it?
- We like it?
私はもっと具体的な利用例や需要が示されるまでは、いらないと思う。
[[noreturn]]をmainに指定した場合、staticストレージのオブジェクトの破棄をしなくてもよいという意味にしようという提案。
組み込み環境など、mainから戻ることがなく、staticストレージ上のオブジェクトを破棄するコードすら許容できないような厳しい資源の制約のある環境の場合に役に立つ。
私は、別のattributeにしたほうがいいと思う。
N3279で、標準ライブラリにnoexceptを付ける際のガイドラインを設けた。
数年の経験を経た今、そのガイドラインを改良する必要がある。
問題なのは、デバッグモードでムーブコンストラクターが例外を投げる可能性だ。そこで、C++17では、std::stringのムーブコンストラクターからnoexcetptを取り除く決定がなされた。
しかし、一般にnoexceptを取り除きたくはない。そこで、ガイドラインでは、「noexceptにすることが大変望ましい」として、要求はしないようにする。
f( a, b, c ) のような式があった場合、sub-expressionであるf, a, b, cの評価順序は、規格上未規定である。もし、sub-expressonが同じオブジェクトを変更していた場合、プログラムの挙動は未定義になってしまう。f( i++, i ) や v[i] = i++ のような式の挙動は未定義である。
例えば、以下のコードは未規定である。
#include <map>
int main() {
std::map<int, int> m;
m[0] = m.size();
}
論文は言う、「この手の質問は、単なる娯楽ではなく、採用面接問題でもなく、学術的な興味によるものでもない。現在規格が規定する式の評価順序は、有害で、有名なプログラミングのイディオムに反し、標準ライブラリの安全な使用にも悪影響を与えている。この罠は素人や注意深くないプログラマーのみ引っかかるものではない。ルールを知っているものですら陥る。」
例えば、以下のコードだ。
void f()
{
std::string s = “but I have heard it works even if you don’t believe in it”
s.replace(0, 4, “”).replace(s.find(“even”), 4, “only”).replace(s.find(“ don’t”), 6, “”);
assert(s == “I have heard it works only if you believe in it”);
}
このコードの挙動は未規定である。
ちなみに、このコードは世界的なC++エキスパート達から査読を受けた、The C++ Programingu Language 4th(プログラミング言語C++第四版)に存在するものである。最近、このコードの問題がツールによって発見された。
このコードを、トリッキーなメソッドチェイニングを使うから悪いのだと避難するのは当たらない。std::cout << a << b << cですらチェイニングである。また、std::future<T>にthen()メンバー関数を追加して、メソッドチェイニング的に使わせようと設計している今、問題はチェイニングではないのだ。根本的な問題を直さなければならない。
論文では、以下の評価順序を提案している。
以下の式は、a, b, c, dの順に評価される。
また、オーバーロード演算子の評価順序は、関数呼び出しのルールではなくて、対応する組み込み演算子の評価順序に従う。
これは歓迎すべき提案のように思われる。sub-expressionの評価順序は定めるべきだ。
標準ライブラリの関数オブジェクトの比較はtotal orderingを要求しているが、関数オブジェクトが関数ポインターの場合ですら適用されてしまう。ポインターに対する組み込みの比較はtotal orderingではないというのに。
関数ポインターの場合は、組み込みの比較と同じ結果になるように一文を加える修正。
ドワンゴ広告
この記事はC++WG JPの会議に出た後にドワンゴに出勤して書かれた。
今夜はボドゲでもしようとおもう。
ドワンゴは本物のC++プログラマーを募集しています。
ドワンゴはFPGAの扱えるハードウェア開発エンジニアを募集しているようだ。
【ニコニコ事業】ハードウェア開発エンジニア (正社員)|職種一覧|中途採用・アルバイト採用・障がい者採用|採用情報|株式会社ドワンゴ
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
妖怪ハウスで12月にボドゲ会をしようと思う。まだ日程を決めていない。いつがいいだろうか。
覆面銃武装マンの都合が日曜日なので、今回は日曜日にしようかと思う。
となると、7日、14日、21日のいずれかだが、いつがいいだろうか。週末まで上の調整さんで多数決を募りたい。
今回は、カレーに加えてピザも作ろうと思う。
トライグラフ除去の可否を問う投票で、賛成票が多数派であったが、その中でIBMは強く反対の立場であった。トライグラフは現に利用者がいて、ASCIIが多数派なC++では、マイノリティといえど、実際の利用者がいる機能を除去するのは、C++のためにならないという主張である。そこでBjarne Stroustrupから、その現実の利用者をまとめて報告せよと言われたので、IBMはこの文書を提出した。
IBMによれば、機密のため顧客名は明かせないものの、現実に利用者が多数いるという。特に重要な利用者の、ある北アメリカの大手の某銀行は、IBMメインフレームを使い続けている。処理は極めて重要である。彼らは長年の多大な労力をデバッグにつぎ込んでおり、安定していて、他の環境に移行することは決してない。
C++はASCIIが多数派であるが、物言わぬマイノリティの声を代弁するためにもIBMはトライグラフ除去に反対するという。
どうも私にはよくわからない世界だ。
TSに提案されているFile systemライブラリに持ち上がっている問題集
40年前のコピペ技術である#inludeにかわるモジュール機能の提案。
現在、C++が使っているコンパイルとリンクモデルは、C言語から受け継いだものである。各翻訳単位は、他の翻訳単位のことを一切知らずに処理される。翻訳単位を超えるには、名前リンケージ(シンボル名)を使う。名前に対応する定義はひとつだけでなければならない(ODR)。
翻訳単位の外に見える名前である外部名を、手動によるコピペで管理することを防ぐために、我々は40年前の技術である自動コピペ技術、プリプロセッサーによるヘッダーファイルを未だに使っている。これは、コンパイラーからみれば、コピペである。
ヘッダーファイルの中身はマクロによって書き換わってしまうおそれがあり、誤りの元である。
変数や関数の宣言ぐらいしか書かれていなかった昔のC言語ならば、まだ十分に耐えられたのだが、モダンなC++では、ヘッダーファイルには実行されるコードが記述されている。コンパイラーは翻訳単位ごとに重複した内容を処理しなければならず、現代のC++のコンパイル時間の増大の原因になっている。
この論文では、C++に近代的なコンポーネント化をもたらすための機能、モジュールの設計を考案している。
モジュールは、既存のプリプロセッサーと組み合わせて使うことができる。これは穏やかな移行のために必要である。既存のプログラムはプリプロセッサーによる自動コピペ技術でコンポーネント化されていて、自動的にモジュールに書き換わってはくれない。今後もしばらくはぷりプロセッサーを使う必要がある。この地上からプリプロセッサーを一掃して、純粋なモジュールのみを使う素晴らしき新世界になるまでは。
さて、モジュールの使い方だが、論文では以下のような文法を想定している。
import std.vector ; // #include <vector>
int main()
{
std::vector<int> v ;
}
モジュールを使う側からすれば、#includeの機械的な置換ぐらいで対応可能だ。
では、ライブラリ側がモジュールに対応するにはどうすればいいのだろうか。これには二つの設計が考えられる。コンパイラー側が自動的に対応する方法と、ソースファイル中に宣言を記述するものだ。論文では、コンパイラーに推測させるよりも、明示的な宣言の方が好ましいとしている。
モジュールは、モジュール宣言で宣言する。
module module-name ;
この宣言の意味は、この翻訳単位のこれ以降の宣言は、module-nameという名前のモジュールの一部であるという宣言だ。モジュール宣言は翻訳単位に一つしか存在できない。前回の論文では、翻訳単位の最初の宣言でなければならないとしていたが、今回の論文では、そのような制約は必要ないということで緩和された。移行を簡単にするために、module宣言の前に宣言されているものは、グローバルモジュールに属するものになる。
モジュールによるインターフェース宣言は、実装と同じソースファイルに記述することもできるし、別のソースファイルに記述することもできる。これは、どちらか片方だけに制限するのは現実的ではないためだ。
モジュールは外部へのテントリーポイントを、export宣言で宣言する。
export toplevel-declaration
struct X { } ;
module M ;
struct Y { } ;
export struct Z { } ;
このように記述した場合、Xはグローバルモジュールに属する。YはモジュールMに属するが、エクスポートされていない。ZはモジュールMに属するexportされている名前だ。
toplevel-declarationは、import宣言かmodule宣言か、通常の宣言のいずれかである。export宣言にimport宣言を指定した場合、import宣言で指定したモジュールがエクスポートする宣言が、現在のモジュールから見えることになる。
module lib ;
export import std.vector ;
export namespace lib {
class Foo
{
std::vector<int> v ;
} ;
}
export宣言でmodule宣言を指定すると、現在のモジュールのサブモジュールの名前を宣言したことになる。
export module module-name ;
export宣言でトップレベルの宣言をすると、その宣言は外部に公開される。
module foo
// barはfooをimportしてもimportされない
import bar ;
// fooをimportするとhogeもimportされる
export import hoge ;
// 内部
void f( int ) ;
// 外部に公開される
export void f( int, int ) ;
// 外部に公開される
export namespace foo {
void g() ;
}
まだまだ文法は変わるだろうし、具体的な実装が出てきてからが面白いところだろう。
memory_order_consumeの存在意義の解説。特にロックやアトミックのような命令を発行することなく、コンパイラーの一部の最適化を抑制するだけで実装可能だが、なかなかそういう高品質の実装が出てこない現状について書いている。
虚空から現れいでたる(Out Of Thin Air)値とは、何の脈略も関連性もなく現れる値のことである。例えば、何らの排他的制御も同期処理も行わずに同じオブジェクトに複数のスレッドから値を書き込んだ場合、その結果の値は未定義となる。未定義である以上、結果の値がどのようになってもかまわない。しかしまったく脈絡のない値が出てきてもいいものか。
議論の結果、規格では実装に対しOOTAを避けるように記述されているが、具体的なOOTAについて記述されていない。この論文では、具体的なOOTAについて記述している。
std::randを廃止したい。しかし、std::randはあまりにもお手軽に使うことができる。
1から6までの乱数をひとつ生成したいとする。大昔のrandならば、以下のように書ける。
// このようなコードを書いてはならない。
int main()
{
std::srand( std::time( nullptr ) ) ;
int pip = std::rand()%6 + 1 ;
}
まずstd::time()で初期化するのはお勧めできないし、剰余を使う方法は、均一に分布しない。
では、C++11ではどう書けばよいのか。
// 長い
int main()
{
std::random_device d ;
std::seed_seq s = { d(),d(),d(),d(),d(),d(),d(),d(),d(),d() } ;
std::mt19937 engine( s ) ;
std::uniform_int_distribution dist( 1, 6 ) ;
int pip = dist( engine ) ;
}
このコードは毎回初期化されるし、均一に分布するが、やや長過ぎる。C++11の乱数ライブラリをまともに使うには、乱数エンジンやら乱数分布やらの概念を学ばなければならない。
std::randのような手軽さを持っていて、モダンな小さい乱数ライブラリが欲しい。そこでこの論文では、そういうライブラリを提案している。
int main()
{
// プログラムの実行ごとに別の状態に初期化される
std::seed_init() ;
int pip = std:randint( 1, 6 ) ;
}
結局、手軽でなければ普及しないのであれば、入るべき提案だろう。
型安全なunionであるvariantライブラリの提案。Boost.Variantに似たインターフェースになっている。ビジターなし、Variantを再帰的に使った場合の特殊な対応なし。
int main()
{
std::experimental::variant< int, double > v ;
v = 0 ;
v = 0.0 ;
int value = std::experimental::get<double>(v) ;
}
テンプレート仮引数に指定した型のみ入れられる型安全なunionだ。
論文で言及しているが、variantは空の状態になりうるという。
variant< T, U > a = T() ;
variant< T, U > b = U() ;
a = b ; // 空になりうる
この場合、aにbを代入するために、まずaに入っているTのオブジェクトを破棄して、次にUを構築するが、Uの構築に失敗した場合は、空の状態になる。
universal-character-nameに対する実装の挙動の提案。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
最近、街コロというボードゲームに興味を持っているが、いまいち戦略が見いだせない。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
Chromium Blog: The Final Countdown for NPAPI
chromiumがNPAPIを殺す予定が公開されている。
すでにNPAPIはデフォルトでブロックされていて、一部の有名なプラグインのみにホワイトリスト方式で許可されている。2015年にそのホワイトリストが取り除かれるので、すべてのプラグインがデフォルトでブロックされる。
2015年4月にNPAPIは無効になる。この時点ではまだ、移行措置のために、chrome://flags/#enable-npapiで有効にできる。
2015年9月には、その移行措置も消される。
やれやれ、ようやくNPAPIが消えてくれるか。しかし、Flashはまだ死にそうにない。
C++の機能が実装されているかどうかを確かめるプリプロセッサーマクロの提案。
現実のコンパイラーは、言語の新機能をすこしづつ実装していく。そこで、現実のコードでは、以下のようなコードが存在する。
struct X
{
X() { ... }
X( X const & rhs ) { ... }
#ifdef __cpp_rvalue_references
X( C && rhs ) { ... }
#endif
} ;
このようなマクロは、現実のコードで頻出するので、規格で定義したほうがよい。そこで、規格にこのようなマクロがあらかじめ定義されているようにする提案。たとえば、この__cpp_rvalue_referencesというマクロは、実装がrvalueリファレンスに対応している場合のみdefineされる。
詳しくは論文を読んでもらうとして、各機能に対応するマクロ名がある。
また、特定のincludeファイルが存在するかどうかを調べる__has_include()や、特定のattributeがあるかどうかを調べる__has_cpp_attributeがある。
#if __has_cpp_attribute(deprecated)
[[ deprecated ]]
#endif
void f() ;
#if __has_include(<unordered_map>)
#include <unordered_map>
#else
#include "hash_map"
#endif
必要悪とはいえ・・・プリプロセッサーマクロに頼る機能には複雑な思いがある。
アライメント調整のための関数を追加する提案。
is_align( x, a )は、xが0か、xがaの倍数の場合にtrueを返す。
align_up( x, a )は、x以上の最小の数nで、is_align(n, a)を満たすnを返す。
align_down( x, a )は、x以下の最大の数nで、is_align(n, a)を満たすnを返す。
int main()
{
std::is_aligned( 128, 64 ) ; // true
std::is_aligned( 32, 64 ) ; // false
std::align_up( 128, 64 ) ; // 128
std::align_up( 128, 256 ) ; // 256
std::align_down( 128, 64 ) ; // 64
std::align_down( 128, 256 ) ; // 128
}
xは、整数型とポインター型でオーバーロードされているので、アドレスのアラインにも使える。
int main()
{
void * ptr = std::malloc( 1000 ) ;
// 16バイトにアライメントされているか?
bool b = std::is_aligned( ptr, 16 ) ;
// 256バイトにアライメント調整されたポインターを得る
void * aligned_256 = std::align_up( ptr, 256 ) ;
// 128バイトにアライメント調整しなおしたポインターを得る
void * aligned_128 = std::align_down( aligned_256, 128 ) ;
またN4201はアライメントキャストを提案している。これは、reinterpret_castとアライメント調整を同時に行うキャストである。
void f( void * ptr )
{
// alignof(int)にalign_upしてint *型にreinterpret_castしたポインターを得る
int * int_ptr = std::align_up_cast< int * >( ptr ) ;
// alignof(short)にalign_downしてshort *型にreinterpret_castしたポインターを得る
short * short_ptr = std:align_down_cast< short * >( ptr ) ;
// 128にalign_up
int * int_ptr = std::align_up_cast< int * >( ptr, 128 ) ;
}
これらの関数は、SIMDや非同期IOやドライバーなど、様々な分野で使われる。いずれも一行で実装できるものであるが、標準ライブラリに存在することによって、必要になるたびにソフトウェアごとにプログラマーが独立してググり、書き、テストする手間を省くことができる。
C++11から追加されたbitset<N>は、ビット列を扱う効率的で抽象度の高いコンテナーとして設計された。しかし、現実のビット列を扱う分野では、アライメントを指定する必要があったり、サイズが重要であったりして、bitsetは事実上使えない状態になっている。
bitsetの内部的なストレージのアライメントやサイズを指定する方法はない。例えば、x86-64のGCCにおけるbitsetの実装は、最小でも8バイトのサイズがある。
bitsetに新たなテンプレートパラメーターを付け加えて、bitset<N,T>とする、Tはalignof(T)とsizeof(T)のための型だ。このbitset<N,T>は、以下のような特性を持つ。(小文字のnがよくわからないが)
static_assert(is_integral<T>::value);
static_assert(alignof(bitset<N,T>) == alignof(T[N / (sizeof(T) * CHAR_BIT) + (n % (sizeof(T) * CHAR_BIT) != 0)];
static_assert(sizeof(bitset<N,T>) == sizeof(T[N / (sizeof(T) * CHAR_BIT) + (n % (sizeof(T) * CHAR_BIT) != 0)];
ビット列を表現するための内部的なストレージの型として、underlying_typeというネストされた型名を追加する。
using type = std::bitset<64>::underlying_type
また、bitsetのエイリアステンプレートとして、速度を重視したfast_bitsetと、サイズを重視したsmall_bitsetを追加する。
int main()
{
// 速度を重視したアライメントやサイズになる
std::fast_bitset<20> fast_bits ;
// 速度よりも可能な限り最小サイズを重視
std::small_bitset<20> small_bits ;
}
タイトルの通り、高速にASCII文字を処理するライブラリの提案。
isdigit, isxdigit, islower, isupper, isalpha, isalnumなどの各種の分類のための関数、todigit, tolower, toupperなどの変換関数、int型を数字一文字に変換するfromdigitもある。
void f( char c )
{
std::ascii::isdigit(c) ;
std::ascii::islower(c) ;
char C = std::ascii::touppwer(c) ;
}
C言語から受け継いだ<cctype>にもほぼ同等の関数群があるが、この論文はcctypeが極めて遅いという設計上の問題を挙げている。
<cctype>は、実行時に設定されるロケールによって処理が異なる。たとえば、isspaceはロケールによって空白文字であると認識される文字が変わる。そもそもASCII互換ですらない文字コードすら扱えるようになっている。このため、cctypeの実装には動的な分岐が必要で、肥大化して遅く、インライン展開もできない。一方、この提案は、ASCII文字を扱うことだけを前提に設計されている。実装はconstexpr関数であり、インライン展開でき、コンパイラーの最適化次第でベクトル化も可能となる。
その他、isXXX系の関数は<cctype>とは違い、int型ではなくbool型を返すことや、char, wchar_t, char16_t, char32_tに対するオーバーロードがあることなど、より洗練された設計になっている。
スレッドの動機に使うラッチとバリアーライブラリの提案。
ラッチは、カウンターを持つ、arrive()かcount_down(N)で、カウンターを減少させる。カウンターが0になると同期条件が満たされる。同期条件が満たされると、wait()で待っているスレッドのブロックが解かれる。
int main()
{
// カウンターは10
std::latch c(10) ;
for ( int i = 0 ; i != 10 ; ++i )
{
std::thread t( [&](){
// 処理
c.arrive() ;
} ) ;
t.detach() ;
}
// カウンターが0になるまでブロック
c.wait() ;
}
arriveは、ひとつのスレッドは一度しか呼び出すことができない。count_downにはそのような制限がなく、同一スレッドから何度でも呼び出せる。count_downは、スレッドプールなどの同一スレッドで複数の単位の処理をしている場合に使える。
ラッチは使い捨てで、再利用できない。バリアーは、同期条件を満たした後にリセットされ、再利用できる。
flex_barrierはbarrierに似ているが、コンストラクターで同期条件を満たした場合に呼ばれる関数オブジェクトを指定できる。
コンセプトのドラフト。
C++の新機能を扱うEWGで現在認識している問題、解決済みの問題、却下された問題の一覧。
const性を伝播させるためのラッパー、propagate_constライブラリの提案。
constなメンバー関数からポインターを経由して別のメンバー関数を呼び出す場合、const性が伝播しない。
struct A
{
void f() {}
void f() const {}
} ;
struct B
{
std::unique_ptr<A> ptr ;
B() : ptr( std::make_unique<A>() { }
void f() { ptr->f() ; }
void f() const { ptr->f() ; } ;
}
int main()
{
B b ;
b.f() ; // call B::f(), A::f()
B cb ;
cb.f() ; // call B::f() const, A::f()
}
このような例でconst性をメンバーに伝播させたい場合に使えるラッパーライブラリを提案している。
struct A
{
void f() {}
void f() const {}
} ;
struct B
{
std::propagate_const<std::unique_ptr<A>> ptr ;
B() : ptr( std::make_unique<A>() { }
void f() { ptr->f() ; }
void f() const { ptr->f() ; } ;
}
int main()
{
B b ;
b.f() ; // call B::f(), A::f()
B cb ;
cb.f() ; // call B::f() const, A::f() const
}
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ところで、歌舞伎座タワーのB2Fのエレベーターの前にいる案内員は、一般の観光客とドワンゴ社員を人目で見分ける能力を有しているらしく、観光客がやってくると観光客向け5F行きエレベーターのボタンを、ドワンゴ社員がやってくると7F直通エレベーターのボタンを押す。いったい彼らはどうやってドワンゴ社員かどうかを見分けているのだろうか。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
unary_function, binary_function, ptr_fun, mem_fun, mem_fun_ref, bind1st, bind2nd, auto_ptr, random_shuffleを規格から取り除く提案。
これらはすでに、現行規格でdeprecated扱いになっているものであり、よりよい方法が存在している。
C++11で採用されたbind自体も、lambda式(特にC++14のジェネリックlambda式)があるため、いずれはdeprecatedになるのではないかと論文筆者は記述している。同感だ。
fold-expressionという新たな式を追加する提案。
たとえば、テンプレートパラメーターのそれぞれにoperator +を適用して返す場合を考える。
template < typename T >
auto sum( T && head )
{
return head ;
}
template < typename T, typename ... Types >
auto sum( T && head, Types && ... args )
{
return head + sum( std::forward<Types>(args) ... ) ;
}
int main()
{
sum( 1, 2, 3 ) ; // 6
}
やりたいことは、args$0 + args$1 + ... + args$Nなのに、なぜこんなに面倒な記述をしなければならないのか。
N4191では、fold式というものを提案している。これを使うと、以下のように書ける。
template < typename ... Types >
auto sum( Types ... args )
{
return ( args + ... ) ;
}
この提案は、fold-expressionという新しいprimary-expressionを追加する。fold式は、以下のように書ける。
( args + ... )
これは、以下のようにleft foldでパック展開される。
((args$0 + args$1) + ...) + args$n
right foldさせたければ、以下のように書けばよい。
( ... + args )
これは、以下のようにright foldでパック展開される。
args$0 + ( ... + ( args$n-1 + args$n ) )
括弧は必須である。
fold式が対応するのは、以下の演算子である。
+ - * / % ^ & | ~ = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*
空のパラメーターパックをfoldした場合、一部の演算子の評価結果は、デフォルトの値になる。
Operator | Value when parameter pack is empty |
---|---|
* | 1 |
+ | 0 |
& | -1 |
| | 0 |
&& | true |
|| | false |
, | void() |
これ以外の演算子で、空のパラメーターパックをfoldすると、ill-formedである。
空のパラメーターパックをfold式に渡した場合の挙動をカスタマイズしたい場合は、以下のように書くことができる。
( args + ... + an )
もちろん、right foldもできる。
( an + ... + args )
このように(a + ... + b)と書いた場合、aかbのどちらか片方だけがパラメーターパックでなければならない。
これは興味深い提案だ。
C++のコア言語で既知の問題集。
C++のコア言語で解決済みの問題集
C++のコア言語で、議論の結果、何も対応しないと結論された問題集。
C++11で追加されたatomicオブジェクトを使えば、標準の範囲内で移植性の高い同期処理を記述できる。例えば、以下は教科書や大学の授業でよくあるTTAS(Test-And-Test-And-Set)スピンロックによるmutexの実装である。
struct ttas_mutex
{
ttas_mutex() : locked(false) { }
void lock()
{
while(1)
{
bool state = false;
if(locked.compare_exchange_weak(state, true, memory_order_acquire) )
break ;
while(locked.load(memory_order_relaxed)==state)
; // 値が変わるまでひたすらループ
}
}
void unlock()
{
locked.store(false, memory_order_release);
}
atomic<bool> locked ;
}
ただし、このコードには問題がある。ループ処理がひたすらCPU時間を浪費してしまうのだ。効率的なmutexの実装には、処理速度を遅くしたり、待機したり、OSの提供する同期方法を使ったりなどの方法を組み合わせる必要がある。その実現方法は、環境により異なり、移植性がない。
N4195は、この問題に標準の範囲内で対処できるように、synchronic<T>というライブラリを提案している。
synchronicは、atomicと似ているが、追加のメンバーがある。まず、値が変更された時までブロックするexpect_updateや、ブロックするCASとしてのload_when_not_equal/load_when_equalがある。synchronicを使えば、先ほどのTTAS mutexは、以下のように書ける。
struct ttas_mutex
{
ttas_mutex() : locked(false) { }
void lock()
{
while(1)
{
bool state = false;
if(locked.compare_exchange_weak(state, true, memory_order_acquire) )
break ;
locked.expect.update(state) ;
}
}
void unlock()
{
locked.store(false, memory_order_release);
}
synchronic<bool> locked ;
}
これによって、std::mutexに匹敵するパフォーマンスを得られる。
[[deprecated]]は当初、enumeratorとnamespaceにも適用できることが望まれていた。しかし、attributeの文法上の問題で、記述できないでいた。
N4196は、attributeの文法を変更してenumeratorとnamespaceにもattributeを記述できるようにし、deprecatedも対応させる提案。
namaspace [[deprecated("namespace lib is deprecated.")]] lib { }
enum struct E { enumerator [[deprecated("enumerator is deprecated.")]] } ;
この提案を紹介すると、ある人から、#includeにもdeprecatedを指定したいという声が出てきたのだが、それがどういう意味なのかよくわからない。単にヘッダーファイルの使用をdeprecatedにしたいのであれば、ヘッダーファイルでユニークな名前をdeprecatedで宣言して使っておけばいいのではないかと思う。
// library.h
#ifndef LIBRARY_INCLUDE_GUARD
#define LIBRARY_INCLUDE_GUARD
using LIBRAY_DEPRECATED [[deprecated("library.h is deprecated.")]] = void ;
using USE_LIBRARY_DEPRECATED = LIBRARY_DEPRECATED ;
#endif
N4197: Adding u8 character literals
charひとつで表現できるUCS文字のリテラルを追加する提案。
char A = u8'A' ; // OK, 値は0x41
char unknown = 'A' ; // 値は実装依存
char あ = u8'あ' ; // ill-formed、char一つで表現できない
これにより、標準の範囲内でASCII文字の値をリテラルで記述できるようになった。
非型テンプレート仮引数に渡せるテンプレート実引数には、様々な制限がある。ただし、その制限は、現状にあっていない。ポインター、リファレンス、メンバーへのポインターが特にひどい。
ポインター型は、静的ストレージ上のオブジェクト、もしくはリンケージを持つ関数で、その文法は&entityか配列か関数でなければならない。あるいは任意のnullポインターに評価される定数式。
つまり、以下のようになる。
template < int * > struct X { } ;
int n ;
X<&n> a ; // OK
constexpr int * p() { return &data ; }
X<p()> b ; // ill-formd
constexpr int * q() { return nullptr ; }
X<q()> c ; // OK
この仕様はいかにも変だ。&nはいいのに、p()はよろしくない。これは、文法は&entityでなければならないためである。しかし、nullポインターとなる任意の定数式は許されているので、コンパイラーはどうせp()をコンパイル時に評価しなければならない。そして、結果がnullポインターでなければエラーにするのだ。余計なお世話である。
何故このようなマヌケな制限になっているのかというと、当時のC++は定数式としてのポインターやリファレンスやメンバーへのポインターを扱うのに十分な機能を持っていなかった。constexprがある今でも、その当時の制限を未だに抱えている。
リンケージを持たねばならない制限というのは、exportテンプレートの名残である。
N4198は、この制限を緩和する提案である。
具体的には、静的ストレージ上の完全なオブジェクトを指すポインターやリファレンスに評価される任意の定数式を許可する。これにより、上の例のq()がテンプレート実引数として合法になる。
完全なオブジェクトという制限は、エイリアシングの問題を避けるためである。もし、サブオブジェクトへのポインターやリファレンスでもよいとなると、以下のようなコードで問題になる。
struct A { int x, y } a ;
template < int * > struct X { } ;
using B = X<&a.x + 1> ;
using C = X<&a.y> ;
さて、BとCは同じ型だろうか。この問題を避けるために、完全なオブジェクトでなければならない。
2014年9月4-5日にRedmondで行われたSG1(concurrency)会議の議事録。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
最近、同僚の一人が机の上に出来合いのダンボールハウスを設置して簡易パーティションをつくりだしたらしい。また会社見学の際の観光名所がひとつ増えたようだ。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
これまで、連続したストレージを指すイテレーターに対して、contiguous iteratorというカテゴリーを新たに設けようという提案があった。問題は、contiguous_iterator_tagを導入してしまうと、random_access_iterator_tagが最も下層に存在しているとみなしてハードコードしている既存のコードと相性が悪い。
そこで、今回の提案では、連続するストレージを指すイテレーターから、生のポインターを得るための方法を標準ライブラリで提案するものとなっている。
生のポインターを必要とするレガシーなC APIなどにポインターを渡したい場合に使える。
// ポインターとサイズを取る
// よくありがちレガシーなC API
void legacy_C_API( void * ptr, size_t size ) ;
template < typename ContiguousIterator >
void f( ContiguousIterator first, ContiguousIterator last )
{
auto raw_pointer = std::pointer_from( first ) ;
legacy_C_API( first, std::distance( first, last ) ) ;
}
内部の実装としては、pointer_fromは、内部的にdo_pointer_fromをunqalified nameで呼び出すので、ADLが発動し、それぞれのイテレーターに対応する関数がコンパイル時にADLで発見されるものとなっている。
ADL以外の実装案として、iterator_tratisのstaticメンバー関数という案が出ている。
つくづくコンセプトが欲しい。ADLを悪用するのは汚い。
まず、このPDFに文句を言いたい。このPDFは50ページある。さぞかし長い文書なのだろうと思ったら、なるほど、長いことは長いが、実質はその半分の25ページぐらいの長さだ。なぜ50ページになっているかというと、左右半分、上下の1割ほどは余白である。極めて面積を非効率的に使っているそびえ立つクソのような固定レイアウトのPDFである。
さて、内容はというと、SIMDベクトル型としてのクラステンプレートライブラリの考察だ。かなり詳細に、初歩的なことから書かれている。
SIMD型はベクトルループよりも、並列処理に適さないデータレイアウトを書いてしまった場合が明らかなので、より並列処理しやすいデータレイアウトが書きやすくなる。
valarrayのことはすっかり忘れ去られているようだ。
歴史的経緯としては、そもそもvalarrayはベクトル型であった。当時は、ライブラリとしてベクトル演算を最適化する手法が主流であったので、valarrayもライブラリ実装による最適化を想定していた。しかし、1990年代後半から、Expression Templatesなどのテンプレートメタプログラミングを多用した最適化手法が流行り始め、valarrayは時代遅れになって放置された。誰も標準ライブラリから取り除くべきだという主張しなかったので、valarrayはそのまま残ってしまったのだ。
さて、時代は変わった。コンパイラーの最適化技術は、当時と比べてかなり進歩した。スカラー演算をまとめてベクトル演算にしたり、ループをベクトル演算にしたりといった最適化が行われるようになってきた。しかし、やはりそのような最適化には限界がある。
多くのC++コンパイラーは、Intrinsicsという、アセンブリとほぼ等しいような薄い関数としてSIMD演算を提供している。しかし、これはあまりにも特定アーキテクチャに深く結びついていて、移植性が低い。
そこで、最初からSIMDベクトル型というものがあれば、コンパイラーはベクトル演算のコードを吐く際の最適化のヒントとして使うことができる。valarrayが一周回って最新になった。
問題は、いまさらvalarrayを大幅に拡張して使うのは互換性の問題や、設計上の想定の違いから無理であろうし、やはり独立した新しいライブラリとなるべきだろう。
N4184で考察しているSIMDベクトル型の条件分岐について。
branchは、モダンなCPUには極めて重い処理である。分岐予測をミスれば、それまでの投機実行結果を破棄して処理をやり直さねばならず、100サイクル以上も無駄にする。そのため、最近の多くのアーキテクチャにはconditional moveという命令が追加されている。
また、SIMDベクトル型の比較の結果は、ひとつのbool値ではなく、boolのSIMDベクトル型で得られる。これは既存のif/for/while/switchのような条件分岐の文法では扱えない。
論文では、Maskとwrite maskingという仕組みを考察している。
deleted定義に文字列リテラルを記述できる文法の提案。
void f() = delete("Don't use this function.") ;
int main()
{
f() ; // ill-formed
}
コンパイラーは、この文字列リテラルを何らかの通知メッセージとして利用できる。たとえば、コンパイルエラーメッセージとして出力するなど。
なぜこの機能が提案されているかというと、SFINAEとstatic_assertを組み合わせたような機能が欲しいからだ。
static_assertは、文字列リテラルを指定できる。コンパイラーはこの文字列を、例えばコンパイルエラーメッセージで出力することができる。
template < typename T >
void f( T & x )
{
static_assert( has_xxx<T>::value, "Template argument must has xxx.") ;
}
SFINAEは、テンプレートのインスタンス化を失敗させ、オーバーロード解決の候補から除外することによって、条件に合わない型を弾くことができる。
template < typename T >
std::enable_if_t< has_xxx<T>::value, void >
f( T & x )
{
}
問題は、SFINAEによってテンプレートのインスタンス化を阻害しても、メッセージを出力することができない。static_assertはインスタンス化しなければ働かないのだ。しかし一方で、SFINAEによる選択も行いたい。SFINAEの利点と、static_assertのメッセージの両方が欲しい。
このため、deleted定義に文字列リテラルを記述できる文法が提案されている。
template < typename T >
std::enable_if_t< !has_xxx<T>::value, void >
f( T & x ) = delete("Template argument must has xxx.")
このように、想定に合わない型をわざとdeleted定義されている関数テンプレートが最適関数になるようにしておけば、SFINAEによる選択かつコンパイルエラーかつメッセージが実現できる。
ostreamは競合しないと規定されているが、複数回の出力が分断されずに出力される保証はない。何らかの外部的な同期が必要になるが、同期方法は標準にない。標準にない場合、複数のお互いに非互換な同期処理が自作される。ostreamの同期処理は標準で存在すべきである。
この提案は、色々と設計の変遷を経たうえで、現在は、バッファー案になっている。
...
{
std::ostream_buffer bout( std::cout ) ;
bout << "hello, " << "World!" << std::endl ;
}
...
このようにostream_bufferでラップして、そのオブジェクトに出力すれば、ostream_bufferのオブジェクトの寿命が来た時に、複数回の出力をまとめて、一気に出力してくれる。複数回の出力の間に他の出力がまざることはない。
この論文は、C++に実行時にサイズが決まるクラスを提案している。
よくある実行時にサイズの変わるストレージを扱うクラスを考える。
template < typename T >
class runtime_size_buffer
{
std::size_t size ;
T * ptr ;
public :
runtime_size_buffer( std::size_t size )
: size(size), ptr(new T[size])
{ }
~runtime_size_buffer( )
{
delete[] ptr ;
}
} ;
auto f( std::size_t size )
{
return std::make_unique<runtime_size_buffer>(size) ;
}
このコードは非効率的である。sizeof(runtime_size_buffer)分のストレージを確保して、次に内部的なストレージを確保している。メモリ確保が二回発生している。
しかし、最も効率的な、メモリ確保を一回しかしないコードを書くには、型システムを迂回しなければならない。
struct rsb
{
std::size_t size ;
char buf[1] ;
} ;
rsb * allocate_rsb( std::size_t bufsize )
{
void * raw_ptr = std::malloc( sizeof(rsb) + bufsize -1 ) ;
rsb * ptr = reinterpret_cast< rsb * >( raw_ptr ) ;
ptr->size = bufsize ;
return ptr ;
}
型システムを完全に迂回しなければならない上に、規格上未定義である。
GCC拡張では、このような現実に書かれているコードを支援するために、構造体の最後の配列の要素数を0にすることができる。
typedef struct
{
size_t size ;
char buf[0] ;
} zero_size_array ;
C99では、この現実に行われている手法を規格でサポートするために、Flexible Array Memberを追加した。配列の添字を記述しない文法になっている。この構造体の後に続くメモリーが、配列のアクセスでそのままアクセスできることを規格上規定している。
typedef struct
{
size_t size ;
char buf[] ;
} flexible_array_member ;
しかしこれは、型システムの迂回である。型があるからこそ、コンストラクターやデストラクター、virtual関数、またコードの誤りをコンパイル時に検出したりできるのだ。クラスの後に続くストレージに配列でアクセスできると仮定しても、C++にこの手法をこのまま持ち込むと、以下のような極めて醜悪なコードになってしまう。
// アライメントやパディングなどは適切に動くものとする
template < typename T >
struct rsb
{
std::size_t size ;
T buf[1] ;
rsb( std::size_t size )
: size(size) { }
} ;
// unique_ptrのためのデリーター
template < typename T >
struct rsb_deleter
{
void operator () ( rsb<T> * ptr ) const
{
// 一つづつ破棄
T * iter = std::addressof(ptr->buf[1]) ;
T * end = iter + ptr->size -1 ;
for ( ; iter != end ; ++iter )
{
iter->~T() ;
}
ptr->~rsb() ;
::operator delete( ptr ) ;
}
} ;
template < typename T >
using unique_rsb_ptr = std::unique_ptr< rsb<T>, rsb_deleter<T> > ;
template < typename T >
unique_rsb_ptr< T > make_rsb( std::size_t bufsize )
{
void * raw_ptr = ::operator new( sizeof( rsb<T> ) + sizeof(T) * (bufsize -1) ) ;
// placement newでrsbを構築
unique_rsb_ptr< T > ptr( new(raw_ptr) rsb<T>( bufsize ) ) ;
// placement newで配列の要素を一つづつ構築
for (
T * iter = std::addressof( ptr->buf[1] ), * end = iter + bufsize - 1 ;
iter != end ; ++iter )
{
new(iter) T ;
}
return ptr ;
}
struct Foo
{
Foo() { std::cout << "constructed" << '\n' ; }
~Foo() { std::cout << "destructed" << '\n' ; }
} ;
int main()
{
auto ptr = make_rsb<Foo>( 3 ) ;
}
このようなコードを書くのは面倒で、もしコードに誤りがあったとしても、コンパイル時エラーにはならない。
C++に必要なのは、型システムの恩恵をうけつつ、このような実行時にクラスのサイズを決定できる機能だ。そこでN4188が提案しているのが、実行時にサイズの決まるクラスだ。
この機能のサンプルコードは以下のようになる。
// もちろんテンプレートが使える
template < typename T >
class X
{
const std::size_t size ;
int other_members ;
T buf[size] ;
public :
X( std::size_t size )
: size(size)
{ }
} ;
int main()
{
// 実行時配列の要素数5
auto p1 = std::make_unique( 5 ) ;
sizeof( X ) ; // 配列を除くコンパイル時サイズ
sizeof( *p1 ) ; // 実行時配列を含む実行時サイズ
}
実行時サイズ配列(runtime size array)とは、最後のデータメンバーの配列である。上の例ではbufだ。実行時サイズ配列を持つクラスを実行時サイズクラスと呼ぶ。
実行時サイズ指定子(runtime size specifier)とは、実行時サイズ配列の要素数として指定できる。上の例ではsizeだ。const修飾された整数型で、最初の非staticデータメンバーでなければならない。
実行時サイズクラス型にsizeofを適用すると、コンパイル時のサイズを返す。つまり、配列を除くサイズだ。
実行時サイズクラスのオブジェクトにsizeofを適用すると、実行時のサイズを返す。
論文では、当初の制限として、実行時サイズクラスに以下のような制限を課している。
論文では、後の拡張提案で、これらの制限のうち緩和できるものは緩和するとしている。
さて、コンパイラーはどうやってこの実行時サイズクラスのオブジェクトを構築するのか。通常の型Tのオブジェクトの構築は、以下のとおりである。
上記の実行時サイズクラスX<T>は、以下のように構築される。
実行時サイズ指定子がクラスの最初のデータメンバーであることにより、コンパイラーはコンパイル時に、クラスの実行時のサイズを格納している場所を認識できる。
実行時サイズクラスは、動的ストレージ上にしか確保できない。
型システムの恩恵を受けつつ、C99のFlexible Array Member相当のことができる機能である。
汎用的なスコープガードとRAIIラッパーの提案。
前回からの変更点は、scope_guardがscope_exitに解明されたこと。
スコープガードとは、スコープを抜けた時に実行される処理のことである。例えば、関数から抜けた時に、必ずメッセージを出力したいとする。
void f()
{
if ( !is_ok() )
{
std::cout << "exit function f.\n" ;
return ;
}
try {
new int ;
} catch( ... )
{
std::cout << "exit function f.\n" ;
throw ;
}
std::cout << "exit function f.\n" ;
return ;
}
このようなコードを手で書くのは甚だしく面倒である。
N4189で提案されているscope_exitを使えば、以下のように書ける。
void f()
{
auto guard = std::make_scope_exit( []{ std::cout << "exit function f.\n" ; } ) ;
if ( !is_ok() )
return ;
new int ;
}
scope_exitのオブジェクトは、自動ストレージ上に構築されることを前提に設計されている。オブジェクトが破棄される際に、指定した関数オブジェクトを実行してくれる。そのため、どのような方法でスコープを抜けようとも、必ず実行される。
RAIIラッパーとは、unique_ptrをより汎用的にしたライブラリだ。
解放処理が必要な何らかのリソースがある。例えばFILE *であったり、OSやライブラリの提供するAPIであったりなどだ。これは通常、RAIIという技法を使って管理する。
struct file_resource
{
std::FILE * res ;
explicit file_resource( std::FILE * fp )
: res( fp )
{ }
file_resource( file_resource const & ) = deete ;
file_resource( file_resource && rhs )
: res(rhs.res)
{
rhs.res = nullptr ;
}
file_resource & operator = ( file_resource const & ) = delete ;
file_resource & operator = ( file_resource && rhs )
{
if ( this != &rhs )
{
res = rhs.res ;
rhs.res = nullptr ;
}
}
~file_resource()
{ std::fclose( fp ) ; }
FILE * get() const { return res ; }
} ;
int main()
{
file_resource fp( std::fopen(...) ) ;
}
このようなRAIIラッパーをいちいち自前で書くのは面倒だ。汎用的なRAIIラッパーが標準ライブラリに欲しい。N4189で提案されているunique_resourceを使えば、以下のように書ける。
int main()
{
auto fp = std::make_unique_resource( std::fopen(...), [](auto fp){ std::fclose( fp ) ; } ) ;
std::fputs( "hello", *fp ) ;
}
これにより、汎用的なRAIIラッパーを、わざわざ自分でクラスを書くことなく使える。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
11月14日から11月18日まで、予備自衛官の訓練に出頭してきた。
引っ越したため、京都地本から東京地本に変わった。京都では小さな駐屯地が点在していたので、選ぶことができたが、東京地本では、朝霞駐屯地で訓練が行われるようだ。朝霞駐屯地は、相当に広かった。
さて、ようやく訓練が終わり、今帰宅してこれを書いている。ここ最近、予定が入りっぱなしでだいぶ疲れた。週末は三連休を幸い、どこか息抜きに旅でもしたいものだ。と、こうつぶやいていると、妖怪ハウスの同居人から、温泉を勧められた。温泉、たしかにいい案だ。
問題は、温泉に一人で行く意義が感じられない。単に広い湯船に浸かりたいのであれば銭湯に行けばよい。広い湯船は朝霞駐屯地の隊員浴場で十分に入ったので、いまさら入ってもしょうがない。一緒に行く人が入ればいいのだが。
では、なにか食べに行くというのはどうか。しかし、自衛隊の食事を食べたうえでは、生大抵の食事は見劣りする。それに、美味しいものが食べたいのであれば、自分で作ったほうがよい。それに、やはり楽しい食事には、一緒に食べる人が必要だ。
では、紅葉でも眺めに行くか。しかし、もはやだいぶ肌寒いし、私に風流を解する心はない。風流人の同道が必要だ。
こうして考えてみると、どうやら旅を楽しむためには仲間が必要なようだ。
一人でも楽しめる旅となると、観光名所ではなく、まだ行ったことのない場所に、なにか面白いものを発見する期待を抱きながら、無計画にうろつくことになるだろう。それもそれで面白いかもしれぬ。
それ以外に一人で楽しめる息抜きとなると、読書かゲームぐらいなものだ。
いずれにせよ、次の連休はなにか息抜きに旅でもしたいものだ。
N4188を読んでいて、C言語の規格を参照する必要が出てきたので、C言語の規格を読んだのだが、最近のC言語の配列は、面白いことになっている。
以下はすべて最新のC規格で合法なコードである。
void f( char a[ * ] ) ;
void g( char a[ static 100 ] ) ;
void h( char a[ const volatile restrict ] ) ;
void wtf( char a[ static const volatile restrict 1 ] ) ;
これは、関数のプロトタイプ宣言でのみ許されている。
[*]は、サイズを指定しない可変長配列型である。関数のプロトタイプ宣言スコープの中でしか使えない。[]との違いは、完全形であることだ。
[static n]は、関数を呼び出した際の実引数は、少なくともn個の要素を持たなければならない。
void f( char a[ static 10 ] ) ;
int main()
{
char a[10] ;
f( a ) ; // OK
char b[9] ;
f( b ) ; // ill-formed
}
コンパイラーはこの情報を最適化のヒントなどに使える。
[ type-qualifier-list ] は、よくわからない。この意味を定義する文面が見つからない。一体なんだろうか。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ドワンゴにはボルダリング部がある。今夜は仕事帰りにボルダリングをしようと思う。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
タイトルだけですべてを説明している感があるが、INVOKEの仕様を拡張して、メンバーにアクセスする場合で、クラスに変換可能な場合に対応する。
読む限り、こういうコードが動くようになるのではないかと思うのだが、すでにGCCやClangで動くのでよく分からない。なにか読み間違えているのだろうか。
struct X
{
int data = 0 ;
} ;
int main()
{
std::function< int & ( X & ) > f = &X::data ;
X x ;
f( std::ref(x) ) = 0 ;
auto data = std::bind( &X::data, std::ref(x) ) ;
data() = 0 ;
}
std::bindのplaceholderを拡張する提案。
std::bindには、placeholderという便利な機能がある。_1は第一引数、_2は第二引数というように、実引数をその順番で指定できるのだ。これにより、実引数の順番を入れ替えたり、無視したりといったことができる。
struct Func
{
void operator () () const
{ std::cout << '\n' ; }
template < typename T, typename ... Types >
void operator () ( T && head, Types ... tail ) const
{
std::cout << head ;
(*this)( tail... ) ;
}
} ;
int main()
{
using namespace std::placeholders ;
auto f1 = std::bind( Func(), _1, _2, _3 ) ;
f1( 1, 2, 3 ) ; // 123
auto f2 = std::bind( Func(), _3, _2, _1 ) ;
f2( 1, 2, 3 ) ; // 321
auto f3 = std::bind( Func(), 4, _3, 4 ) ;
f3( 1, 2, 3 ) ; // 434
}
N4171は、このプレイスホルダーを拡張する。すべての実引数を表す_all。 N番目から最後までの実引数を表す_from<N>。最初からN番目までの実引数を表す_to<N>。N番目からK番目までの実引数を表す_between<N, K>。
struct Func
{
void operator () () const
{ std::cout << '\n' ; }
template < typename T, typename ... Types >
void operator () ( T && head, Types ... tail ) const
{
std::cout << head ;
(*this)( tail... ) ;
}
} ;
int main()
{
using namespace std::placeholders ;
auto f1 = std::bind( Func(), _all ) ;
f1( 1, 2, 3 ) ; // 123
f1( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 123456789
auto f2 = std::bind( Func(), _from<5> ) ;
f2( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 56789
auto f3 = std::bind( Func(), _to<5> ) ;
f3( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 12345
auto f4 = std::bind( Func(), _between<3,7> ) ;
f4( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 34567
auto f5 = std::bind( Func(), _all, _all ) ;
f5( 1, 2, 3 ) ; // 123123
auto f6 = std::bind( Func(), _to<3>, _from<6,9>, _between<4,5> ) ;
f6( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; //123678945
}
また論文は、ジェネリックlambda式や汎用ラムダキャプチャーがある現在、std::bindに存在価値はあるのかという議論に対して反論を試みている。std::bindを使ったほうが簡潔で便利な場合があるという。 Perfect Forwardingを自力で書くのは面倒なので、std::bindの簡潔さは魅力だ。
面白いし、簡潔に書ける場合もあるだろうが、私はlambda式の方が好みだ。
名前付き実引数の提案。
name : valueという文法で、仮引数の名前を指定して実引数を渡すことができる。
// グラフィック用のライブラリなどでありがちな引数の多い関数
void draw_rect(int left, int top, int width, int height);
int main()
{
// どれがどれだっけ?
draw_rect( 100, 100, 400, 400 ) ;
// わかりやすい
draw_rect( left: 100, top : 100, width : 400, height : 400 ) ;
// 順番を入れ替えることもできる
draw_rect( width : 400, height : 400, top : 0, left : 0 ) ;
}
これを名前付き実引数(named arguments)と呼ぶ。従来の実引数を、positional argumentsと呼ぶ。positional argumentsは、名前付き実引数の後に書くことはできない。
void f( int a, int b ) ;
int main()
{
f( 1, b : 2 ) ; // well-formed
f( a : 1, 2 ) ; // ill-formed
}
ellipsisやVariadic Templatesを仮引数に使った関数の場合は、名前付き実引数は使えない。
template < typename ... Types >
void f( int a, Types ... args ) ;
void g( int a, ... ) ;
int main()
{
f( a : 0 ) ; // ill-formed
g( a : 0 ) ; // ill-formed
}
コンストラクター呼び出しにリスト初期化を使う場合には、名前付き実引数が使える。
struct X
{
X( int a, int b, int c )
} ;
X x1( a : 1, b : 2, c : 3 ) ;
X x2{ a : 1, b : 2, c : 3 } ;
ただし、提案はアグリゲート初期化には禁止している。
struct X
{
int a, b, c ;
} ;
X x{ a : 1, b : 2, c : 3 } ; // ill-formed
これを禁止する理由としては、後からコンストラクターを追加した場合、仮引数の名前とデータメンバーの名前とで、プログラムの意味が変わってしまうからだとしている。
関数ポインターを経由した関数呼び出しには、名前付き実引数は禁止されている。
void f( int x, int y ) ;
using pointer = void (*)( int a, int b ) ;
pointer p = &f ;
この例で、関数ポインターpを経由して関数呼び出しをする際、xとyはもちろん使えない。関数ポインターが指し示す先は実行時に決まるためだ。aとbも使えない。理由は、aとbを使えるようにするためには、typedef名に仮引数名まで型情報として含めなければならないからだ。
この提案は、関数の型に仮引数名を含める実装方法を取らないことを前提に設計されている。
また論文では、予想される反論に回答を与えている。
そもそも引数の数が多すぎる関数は設計が間違っているという反論に対しては、「それはそうだが、既存の現実のAPIは引数の数が多く、名前付き実引数は現実の問題を解決できる」と。
C99のdesignated initialierと衝突するという反論に対しては、「あちらはデータメンバー名であり衝突しない」と
仮引数の名前が変わると呼び出し側の意味が変わってしまうという反論に対しては、既存の有名なライブラリの最新版と数年前のバージョンのdiffを調査したところ、仮引数名の変更は極めて少なかったという調査結果を示している。
また、将来の拡張案として、デフォルト実引数や、名前付きテンプレート実引数を挙げている。
以下のようなデフォルト実引数が動くようになる。
void f( int a = 0, int b ) ;
int main()
{
f( b : 0 ) ; // OK
}
論文では、この挙動に異論はないが、この提案では含めないとしている。
名前付きテンプレート実引数とは、以下のようなコードのことだ。
template < typename Key, typename Value >
struct map { ... } ;
map< Key : int, Value : string > m ;
論文は、これは別物であり、別の提案で取り上げられるべきだとしている。
operator .をオーバーロード可能にする提案。
class string_ref
{
std::string data ;
public :
string_ref( std::string const & value )
: data( value ) { }
std::string & operator .() { return data ; }
} ;
int main()
{
string_ref ref("hello") ;
ref.length() ; // ref.operator.().length()
std::string str("str") ;
ref = str ; // ref.operator.() = str
}
operator *やoperator ->をオーバーロードしてポインターのように振る舞うクラスを作ることができるように、operator .をオーバーロードすれば、リファレンスのように振る舞うクラスを作ることができる。代入演算子すら使える。
メンバーを明示的に宣言することによって、上書きできる。
// resizeのあとにshrink_to_fitするstring
class shrink_string
{
std::string data ;
public :
std::string & operator .() { return data ; }
void resize( std::size_t n )
{
data.resize( n ) ;
data.shrink_to_fit() ;
}
} ;
用途としては、スマートポインターのようなスマートリファレンスを書くのに使える。
本の虫: C++に提案されている統一関数呼び出し文法(Unified Call Syntax): N4165, N4174を参照。
デフォルトの比較演算子を暗黙的に生成する提案。
前回の提案では、明示的に生成するよう記述した場合のみ生成するようになっていた。これは、既存のコードの意味を変えないという点では素晴らしいが、煩わしい。(a!=b) != !(a==b)がtrueとなるような珍妙なクラスを書いた奴は、根本的な論理的問題を引き起こしているのであって、そんなマヌケを救う必要はない。
とはいえ、色々と考えることがある。
==だけがユーザー定義されていた場合は、!=は==を使って生成される。では逆に!=だけがユーザー定義されていた場合はどうか。論文著者のBjarne Stroustrupは、!=から==を生成するのは好ましくないので生成しないと書いている。
デフォルト生成された比較演算子にエラーがあって生成できない場合は、演算子を使った場合にエラーとなる。これは代入演算子の場合と同じだ。
==の定義は、クラスの各メンバーをそれぞれ比較して、どれかひとつでもfalseを返した場合は、結果がfalseとなる。
!=の定義は、a!=bは!(a==b)と定義される。
クラスがポインター型のメンバーを持っている場合は、比較演算子は生成されない。
リファレンス型のメンバーは、普通に比較される。
配列型のメンバーは、各要素がそれぞれ比較される。
mutableメンバーの扱いは難しい。人によって、mutableはクラスの値の一部であるとも、そうでないとも、意見が分かれる。論文では、
という三つの方法を上げたうえで、3.の無視を提案している。
空のクラスの場合は、等しいと評価される。
unionの場合は、デフォルトの比較演算子は定義されない。ユーザー定義の==がある場合は!=は生成される。
派生がある場合、仮想関数がない場合に、基本クラスを隠しメンバーとみなして比較する。
N4175に対する議論をまとめたもの。
連続したストレージを多次元配列のように扱えるラッパークラスライブラリ、array_viewの提案。
前回からの変更点は、array_viewとstrided_array_viewのコンストラクターの引数の順序を、既存のSTLの慣習に合わせて、{size, location}から{location, size}に変更したこと。定数のviewに対するエイリアステンプレートを追加したこと。
§4.4の文面では、多次元ポインターについて規定しているが、ポインターの配列へのポインターの場合に対する記述がなく、以下のコードが型変換できないという問題がある。
int main()
{
double *array2D[2][3];
double * (*array2DPtr1)[3] = array2D; // Legal
double * const (*array2DPtr2)[3] = array2DPtr1; // Legal
double const * const (*array2DPtr3)[3] = array2DPtr2; // Illegal
}
以下のコードは合法であることを考えると、これはおかしい。
int main()
{
double *array[2];
double * *ppd1 = array; // legal
double * const *ppd2 = ppd1; // legal
double const * const *ppd3 = ppd2; // certainly legal (4.4/4)
}
したがって、現行の文面に従えば、reinterpret_castを使うしか方法がなくなる。
この問題を解決する。
きわめてさらっと触れられているが、この修正案は、reinterpret_castにも手を加えている。なんと、reinterpret_castでconst性を削ぎ落とすことが可能になった。
void f( void const * ptr )
{
// 現行ではill-formed
// N4178提案ではwell-formed
reinterpret_cast< int * >( ptr ) ;
}
いまさらこの制約をあっさりと外すのだろうか。reinterpret_castを使う以上、別にconst性をそぎ落としても問題ないのではないかとは思っていたものの、このような大きな変更にしては意外の軽さだ。
トランザクショナルメモリーの文面案
トランザクショナルメモリーに対するN3999からの変更点。
トランザクショナルメモリーの会議の議事録。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
独自実装のintervalクラスでメンバ関数sin()を実装した。しかし、ジェネリックなコードが書きたい。 sin(x)とかいたとき、intervalならx.sin()を、でなければstd::sin(x)を呼び出したい。 こんなときどうすれば良い?
プライマリテンプレートと明示的特殊化を使って、コンパイル時に条件分岐を行えばよい。
#include <cmath>
#include <type_traits>
struct interval
{
double sin( ) const
{
// unimplemented
return 0.0 ;
}
} ;
// プライマリーテンプレート
// デフォルトの実装
template < typename T >
struct dispatch
{
static double invoke( T const & value )
{
return std::sin( value ) ;
}
} ;
// interval型に対して明示的特殊化
template < >
struct dispatch<interval>
{
static double invoke( interval const & value )
{
return value.sin() ;
}
} ;
template < typename T >
void f( T const & x )
{
double s = dispatch<T>::invoke(x) ;
}
int main()
{
double d = 1.0 ;
f( d ) ;
interval i ;
f( i ) ;
}
以下のように書くこともできる。
struct generic_sin
{
template < typename T >
static double invoke( T const & value )
{
return std::sin( value ) ;
}
} ;
struct interval_sin
{
static double invoke( interval const & value )
{
return value.sin() ;
}
} ;
template < typename T >
using dispatch = std::conditional_t< std::is_same<T, interval>::value,
interval_sin, generic_sin > ;
超会議でボルダリングを体験してから、ボルダリングをやってみたいと常々思っていたが、なかなか機会が得られないまま、日常の雑事に追われていた。思えば最近はあまり運動もしなくなり、日々C++標準化委員会の文章を読んだり、勉強会の発表の準備に追われて鬱々と過ごしていた。最近、睡眠時間が大幅に増加している。
さて、ドワンゴではボルダリング部があり、今週の水曜日の夜にボルダリングを行っている。ボルダリングの施設利用料は、会社から支給される部費からも出すので、自己負担は半額程度になるという。これをいい機会と、今週の水曜日にボルダリングに行ってきた。しかし、どうも気乗りがしない。家に帰って寝ていたくもある。
ボルダリングのジムとしては、B-PUMP TOKYO 秋葉原店に行った。初回登録を行う必要があるのだが、これを極めてストレッスフルなiPad上で行った後、簡単な安全のためのビデオ講習を見せられた。
さて、肝心のボルダリングだ。
来るまではあまり気乗りがしなかったが、実際に体を動かしてみると、これが極めて面白い。やはり、この日頃鬱々として楽しまなかったのは、運動不足のせいだったのだろう。
私が達成できたのは、簡単なものだけであった。難しいものとなると、開始することすらできなかった。
翌日、体がなまっているせいか、普段使わない部分の筋肉を酷使したためか、すっかり筋肉痛になってしまった。日常の諸動作が難しい。財布のチャックを開けたり、キーボードを打つのに不都合がある。これを書いている今も、まだ筋肉痛が残っている。しかし、心地よい筋肉痛だ。鬱々としていた気分もすっかり吹き飛んでしまった。
これから週に一回ぐらいはボルダリングをしたいものだ。また、自前の靴を買い求めるべきだろう。
ボルダリングOFFなるものもしてみたくある。
C++に、値に対する制約を記述するための機能を追加する具体的な文法案などはいくつか提案されている。この論文では、具体的な提案ではなく、値の制約ということに対して、必要な機能と実装上の課題などを考察している。
値の制約という機能は、値に対して何らかの無効であり本来起こりえない条件を指定するものである。
たとえば、ある関数はdouble型の引数をとるが、負数が与えられることは想定していないとかだ。古典的なassertの中に書かれる式も制約である。論文では他にも、同一の表現を指定するなどのことをあげている。たとえばstd::vector vのv.empty()とv.size() == 0は同じ意味であるなど。
論文は実行時のチェックよりも、コンパイル次の静的チェックに重きをおいていて、実行時チェックを自動生成するなどの機能も、Eiffelを例にあげて説明している。
コンテナーを引数に取り、特定の要素を効率的に削除する、erase_if( container, pred )とerase( container, value )の提案。
int main()
{
std::list<int> l = { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ;
// 3を取り除く
std::experimental::erase( l, 3 ) ;
// 5未満を取り除く
std::experimental::erase_if( l, [](auto x){ return x < 5 ; } ) ;
}
モダンなC++のコードにおいては、生のポインターを直接使ったり、deleteを直接呼び出したりするのを避けるべきである。unique_ptrやshared_ptrを使うべきである。これにより、リークフリーでメモリーセーフなコードを記述できる。オブジェクトの寿命が構造化されていなかったり、そもそもいつまで生きているのか分からなかったりする場合は、重要である。特に、並列処理では重宝する。
残念ながら、ロックフリーなコードを書く場合、今だに生のポインターを扱わなければならない。unique_ptr, shared_ptr, weak_ptrは、アトミックな操作をサポートしていないためである。shared_ptrだけは多少のサポートがあるが、貧弱である。
この論文は、atomic_shared_ptr<T>, atomic_eak_ptr<T>, atomic_unique_ptr<T>を提案している。
前回の論文では、atomic<*_ptr<T>>というatomicテンプレートのスマートポインター型に対する特殊化を特別に定義するという珍妙な設計だったが、会議で否定されたために、独自のテンプレートとなった。
2014年10月24日に行われる電話会議の予定表。
&&は、具体的な型に修飾する場合と、テンプレート仮引数に就職する場合とで、意味が異なる。
struct X { } ;
void f( X && ) ;
template < typename T >
void g( T && ) ;
この例で、X &&と、T &&は、受け取れる値が異なる。
X &&はX型のnon-const non-volatileなrvaluesしか受け取れない。
T &&は、CV修飾子の有無はおろか、lvalueさえ受け取ることができる。
auto &&にも同じ性質がある。
テンプレート仮引数やautoに&&を使った場合のこの何でも受け取れるという性質の挙動については、従来、公式に名前が付けられていなかった。
しかし、この概念を教育したり議論したりする際に、名前がないと困る。そこで、この論文では、この性質をForwarding Referenceと呼ぶことを提案している。同時に、標準規格の文面上でもそのように名付ける変更案を提案している。
単に既存のプログラム上の意味に名前を付けるだけで、意味が変わるわけではない。
当初、Scott Meyersがこの性質に名前を付ける必要性を感じ、発表でUniversal Referenceと呼んだが、Universalという名前よりは、Forwardingの方がふさわしいと判断された。これは、どんな値でも転送する性質を持つからだ。
本の虫: C++に提案されている統一関数呼び出し文法(Unified Call Syntax): N4165, N4174を参照。
std::initializer_listの所有する値には、constなポインターを経由してアクセスする。constであるがために、値を変更したり、ムーブしたりできない。
void f( std::initializer_list<int> list )
{
// elemの型はint const &
for ( auto && elem : list )
{
elem = 0 ; // エラー
}
}
void g( std::initializer_list< std::unique_ptr<int> > list )
{
std::vector< std::unique_ptr<int> > v ;
// elemの型はstd::unique_ptr<int> const &
for ( auto && elem : list )
{
v.push_back( std::move( elem ) ) ; // エラー
}
}
しかし、初期化リストの初期化子がすべてリテラルででもない限り、initializer_listは、実装の詳細としては実行時にストレージを所有している。所有している以上、所有権の移動ができるはずである。しかし、現状ではconst_castを用いて、無理やりconst性を消し去る以外に、所有権を横取りする方法がない。
void f( std::initializer_list<int> list )
{
// elemの型はint const &
for ( auto && elem : list )
{
const_cast<int &>(elem) = 0 ;
}
}
void g( std::initializer_list< std::unique_ptr<int> > list )
{
std::vector< std::unique_ptr<int> > v ;
// elemの型はstd::unique_ptr<int> const &
for ( auto && elem : list )
{
v.push_back( std::move( const_cast< std::unique_ptr<int> & >(elem) ) ) ;
}
}
なぜinitializer_listがムーブセマンティクスに対応していないかというと、initializer_list設計当時の2005年から2007年は、まだムーブセマンティクスが一般的ではなく、設計に含めることができなかったためだ。2008年に、initializer_listをムーブセマンティクスに対応させるための提案もN2801として出たものの、時間が足りず、そのまま打ち捨てられた。
この論文では、initializer_listをムーブセマンティクスに対応させるための特殊化、std::initializer_list<T &&>を提案している。rvalueリファレンスは、単にオーバーロード解決でムーブ可能なinitializer_listを受け取るためのフラグであって、それ以外の意味はない。イテレーターは非constなリファレンスを返すので、以下のように書ける。
void f( std::initializer_list<int && > list )
{
// elemの型はint &
for ( auto && elem : list )
{
elem = 0 ; // OK
}
}
void g( std::initializer_list< std::unique_ptr<int> && > list )
{
std::vector< std::unique_ptr<int> > v ;
// elemの型はstd::unique_ptr<int> &
for ( auto && elem : list )
{
v.push_back( std::move( elem ) ) ; // OK
}
}
従来のinitializer_list<T>の挙動は変わらない。
movable_initializer_list<T>のような別のプライマリーテンプレートを使わなかった理由としては、T &&は十分にわかりやすいし、initializer_listはよく知られている名前であるし、また、テンプレートメタプログラミングによって切り替え可能になるからだという。
内部的には、initializer_list<T && >はinitializer_list<T>から派生している。
並列実行版アルゴリズムに、transform_reduceの追加をする提案。
ドット積の計算を考える。従来のシリアル実行ならば、std::accumulateを使うことで、以下のように書ける。
struct Point {
double x, y;
};
std::vector<Point> values(10007, Point{2.0, 2.0});
double result =
std::accumulate(std::begin(values), std::end(values), 0.0,
[](double result, Point curr)
{
return result + curr.x * curr.y;
});
しかし、これは並列化できない。処理が進むためには、まず前回のresultが計算されなければならないからだ。
この問題を解決するには、並列版アルゴリズムの提案に含まれているreduceを使うことができる。ただし、reduceの制約上、極めて不便なworkaroundを余儀なくされる。
Point result =
std::experimental::parallel::reduce(
std::experimental::parallel::par,
std::begin(values),
std::end(values),
Point{0.0, 0.0},
[](Point res, Point curr)
{
return Point{
res.x * res.y + curr.x * curr.y, 1.0};
}
);
reduceの戻り値の型は、イテレーターの値の型でなければならない。そのため、本来ならdoubleを使うべきところだが、Pointクラスのxメンバーだけを使っている。しかも、無理やり行列的な計算をしている。値をそのままにしておくためだけに、本来不必要な1.0での乗算を行わなければならない。
そこで、N4167は、イテレーターの値の方から戻り地の型へ変換する関数オブジェクトを引数に取る、transform_reduceを提案している。
double result =
std::experimental::parallel::transform_reduce(
std::experimental::parallel::par,
std::begin(values),
std::end(values),
0.0,
std::plus(),
[](Point r)
{
return r.x * r.y;
}
);
auto_ptrを規格から削除する提案。
すでにauto_ptrはdeprecated扱いで、Annex D互換機能に移されているが、この提案で完全に削除する。
auto_ptrは、もはや使うべきではない。auto_ptrを使う既存のコードは、速やかにunique_ptrに書き変えるべきである。
std::funcitonやstd::bindなどで使われている、INVOKE(§20.9.2)通りに呼び出しをしてくれる関数テンプレートinvokeを標準ライブラリに追加する提案。
std::functionを実装してみた人間ならばわかると思うが、INVOKEの仕様は結構複雑だ。
struct X
{
int data ;
int f() { return 0 ; }
} ;
int f() { return 0 ; }
int main()
{
// まあ、よくある関数呼び出し
std::function< int () > f1 = &f ;
f1() ;
// メンバー関数呼び出し
std::function< int ( X & ) > f2 = &X::f ;
X x ;
f2( x ) ;
// メンバー関数呼び出しのポインター版
std::function< int ( X * ) > f3 = &X::f ;
f3( &x ) ;
// データメンバーへのアクセス
std::function< int & ( X & ) > f4 = &X::data ;
f4( x ) = 123 ;
// データメンバーへのアクセスのポインター版
std::function< int & ( X * ) > f5 = &X::data ;
f5( &x ) = 123 ;
}
std::functionは、実は、データメンバーへのアクセスまで関数呼び出し風の文法でサポートしていたのをご存知だろうか。INVOKEの仕様に従っているためである。
この挙動には、SFINAEを利用したメタプログラミングが必要である。これはよく使うので、標準ライブラリにほしい。そこで、そのような挙動をするinvoke関数テンプレートを追加する。
ちなみに、リファレンス実装は以下の通り
template<typename Functor, typename... Args>
typename std::enable_if<
std::is_member_pointer<typename std::decay<Functor>::type>::value,
typename std::result_of<Functor&&(Args&&...)>::type
>::type invoke(Functor&& f, Args&&... args)
{
return std::mem_fn(f)(std::forward<Args>(args)...);
}
template<typename Functor, typename... Args>
typename std::enable_if<
!std::is_member_pointer<typename std::decay<Functor>::type>::value,
typename std::result_of<Functor&&(Args&&...)>::type
>::type invoke(Functor&& f, Args&&... args)
{
return std::forward<Functor>(f)(std::forward<Args>(args)...);
}
これはなぜ標準ライブラリにないのか不思議だった。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
この二つのC++標準化委員会の文章は、統一関数呼び出し文法を提案している。
現在、フリー関数とメンバー関数では、呼び出しの文法が異なる。フリー関数は、f( x, y, z )と呼び出すが、メンバー関数は、x.f( y, z )と呼び出す。これは汎用的なコードを書くのに都合が悪い。
template < typename T >
void f( T x )
{
// Tがクラス型である場合これを使いたい
x.swap( 1, 2 ) ;
// Tがクラス型ではない場合これを使いたい
f( x, 1, 2 ) ;
}
現状では、このようなコードは、テンプレートメタプログラミングの技法を駆使して、コンパイル時条件分岐を行わなければならない。この問題は、単に関数を呼び出す文法が統一されていないがために起こるのである。関数を呼び出す文法を統一してしまえばいい。
つまり、x.f(y)が、f(x,y)と同じ意味になるようにしてしまえばいいのだ。x.f(y)は、まずxのメンバーfを探す。見つからない場合は、f(x,y)と置き換えてfを探す。
struct X
{
void f( int ) ;
} ;
struct Y { } ;
void f( Y &, int ) ;
int main()
{
X x ;
x.f( 0 ) ; // call X::f( 0 ) with x as this pointer
Y y ;
y.f( 0 ) ; // call f( y, 0 )
}
これが、N4165の提案だ。
N4174は、さらに、f(x,y)をx.f(y)と同じ意味にする提案もしている。すなわち、f(x,y)と書けば、フリー関数fがない場合、x.f(y)として呼び出してくれる。ただし論文では、フリー関数fが存在したとしても、メンバー関数を優先すべきであるとしている。
struct X { } ;
void f( X &, int ) ;
int main()
{
X x ;
x.f(0) ; // call f( x, 0 )
}
論文ではさらに、ツールサポートの充実を主張している。
テキストエディターでコードを記述する際に、既存の名前の一覧から、自動的に部分一致する名前を探しだして補完してくれる機能は、すでに一般的だ。
問題は、フリー関数の場合、その文脈で入力できる名前の候補があまりにも多いため、自動補完があっても面倒である。
そこでこの提案だ。たとえば、CライブラリのFILE構造体への操作だが、この提案が採用された場合、
FILE * fp = fopen(...) ;
fp->fseek
このように、FILE *を第一引数に取る関数だけに候補を絞り込める。
さらに提案では、fputsのような第一引数ではない関数にも対応するため、引数の場所をコンパイラーがよきにはからって決定してくれる案に言及している。
なるほど、論文の主張するツールサポートは頼もしそうに見えるが、問題は、stdio.hはC++ではdeprecated扱いで、cstdioを使うべきである。その場合、FILE構造体はstd名前空間スコープの中に入る。
すると何が起こるかというと、std名前空間が、ADLの連想名前空間に入る。std名前空間には、move, swap, addressofを始めとした、大量の汎用的すぎる関数テンプレートがある。
std::FILE * fp ;
fp->move
それだけではない。FILE構造体は通常ポインターとして使う。ポインターであるということは、たとえば<algorithm>をincludeしていた場合、イテレーターを第一引数に取る大抵の関数テンプレートが候補に上がってしまう。
fp->for_each
実際には使うことを想定していないこれらの名前で候補が膨れ上がってしまう。しかもこれは、既存のクラスの候補に及ぶ。標準ライブラリのクラスのメンバーの補完候補が大量の利用を想定していないがたまたまシグネチャが一致する関数名で膨れ上がってしまうし、intやdoubleやポインターといった基本的な型や、そういった型へのユーザー定義変換関数を定義しているクラスには、大量の候補で膨れ上がることになる。
どうもこの論文の主張するツールサポートの向上には賛同しがたいものがある。自動補完の候補は確かに減らせるが、それほど減らせるわけではなく、むしろゴミが増えてしまう。
色々と問題があるものの、関数呼び出しの文法の違いで汎用的なコードが書けないという問題は面倒なので、解決されて欲しい。
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
2014年11月1日に広島市立大学で行われた、すごい合同勉強会に参加して、発表してきた。
他人の発表で特に印象に残ったものを挙げると、
井上 博之氏の自動車の内部ネットワークをいじる話は興味深かった。自動車の有線の制御線であるCANバスに接続してコマンドを送信することで、ドアの開閉や速度計や警告灯などの変更が行えることを実証したという話であった。
さて防御方法であるが、そもそも物理アクセスできる以上、どうしようもないのではないかという気もする。
ザキさんのMay the Team be with you (episode1)、受託開発の闇を見た。
@eielhの内包表記(仮)、私にはよくわからない話だった。
にしもつ氏のスクリーンリーダーNVDA日本語版の思い出(仮)、視覚障害者向けのスクリーンリーダーの開発や普及活動などについて話していた。
ねむねむ氏の移動透過通信は、移動する無線通信装置が、移動の結果異なるネットワークに接続した場合でも、既存の通信をそのまま維持する仕組みで主要なものを紹介し、利点と欠点をまとめた発表であった。
障害者向けのソフトウェアは、誰でも使えるようになるべきである。というのも、もし健常者が入手できないようなソフトウェアは、障害者がその使い方を謙譲さに質問することすらできない。あまり使われないソフトウェアは適切な利用者のコミュニティが形成されずにきわめて使いづらくなる。
さて、肝心の私の発表であるが、今回はC++を知らない参加者も多いであろうから、ソースコード数行で解説できる、最近のC++の新機能の提案を軽く紹介することにした。