本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

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

P0060R0: Function Object-Based Overloading of Operator Dot

operator .をオーバーロード可能にする提案。

この提案は、リファレンスを返す提案とは異なり、コンパイラーが関数オブジェクトを生成する提案となっている。

operator .をオーバーロードしてるユーザー定義型がある。

struct X
{
    // なにもしない
    template < typename T >
    void operator .( T && ) { }
}

そのような型の非staticデータメンバーの名前検索(たとえば.some_name)をして、名前が見つからない場合、コンパイラーは以下のような型を生成し、そのオブジェクトをoperator .のオーバーロード関数の実引数に渡す

struct synthesized_function_type {
    template<class T>
    auto operator()(T&& t) -> decltype(t.some_name) noexcept(t.some_name) {
        return t.some_name;
    }
};

その結果、以下のように書ける。

X x ;
x.hoge ; // OK
x.hage ; // OK

メンバー関数の場合は、以下のような型が生成される。

struct synthesized_function_type {
    template<class T, class... Args>
    auto operator()(T&& t, Args&&... args) -> decltype(t.some_name(std::forward<Args>(args)...)) noexcept(t.some_name(std::forward<Args>(args)...)) {
        return t.some_name(std::forward<Args>(args)...);
    }
};

これを使って、例えばスマートリファレンスを作成できる。

template < typename T >
class Ref
{
    T t ;
public :
    template < typename F >
    auto operator . ( F && f )
    {
        return f( t ) ;
    }

    template < typename F, typename ... Args >
    auto operator . ( F f, Args && ... args )
    {
        return f( std::forward<Args>( args ) ... ) ;
    }
} ;

struct Point
{
    int x, y ;
    void func( int x ) { }
} ;

Ref<X> r ;

r.x ;
r.y ;
r.func( 0 ) ;

P0061R0: Feature-testing preprocessor predicates for C++17

SG-6が推奨している機能テストマクロに含まれている、__has_includeと__has_cpp_attributeを正式にC++17に提案している。

__has_includeは指定したヘッダーファイルがあるかどうかを調べることができる。__has_cpp_attributeはattributeが存在するかどうか調べることができる。

#ifdef __has_include(<optional>)
#   include <optional>
#else
#   include "my_optional"
#endif 

P0062R0: When should compilers optimize atomics?

atomicではすべての最適化が無効になると考えている者もいるが、atomicは最適化できる。たとえば、load( x, memory_order_x ) + load( x, memory_order_x )は、2 * load( x, memory_order_x )に変換することができる。しかし、非volatileなatomic loadが使われてはいないからといって、消してしまうのは異論が多い。

この論文では、c++std-parallelで議論された、異論のある最適化についてまとめている。

// 途中経過表示用の変数
atomic<int> progress(0);

f() {
    for (i = 0; i < 1000000; ++i) {
        // 処理
        ++progress;
    }
}

このようなコードがあるとする。atomic変数progressは処理の進行具合を記録していて、他のスレッドはこの変数を参照してユーザーに処理の進行具合をプログレスバーで表示している。

過剰に最適化するコンパイラーは、以下のようにコードを変換するかもしれない。

atomic<int> progress(0);
f() {
    int my_progress = 0; // レジスタ割り当てされた非atomic変数
    for (i = 0; i < 1000000; ++i) {
        // ...
        ++my_progress;
    }
    progress += my_progress;
}

コードがこのように変換されると、他のスレッドがprogressを参照した時に、0か999999しか見えない。

他には以下のような例がある。

x.store(1); // アトミック操作
while (...) { … } // ループ

コンパイラーは以下のように変換するかもしれない。

while (...) { … }
x.store(1);

これも規格上問題ないが、ループを始める前にアトミック操作を行って他のスレッドに通知したいと考えるプログラマーの意図とは異なるだろう。

Peter Dimovによれば、過剰な最適化は好ましいという。例えば、shared_ptrのカウンターの増減を除去できる。

Paul McKennryによれば、上記のプログレスバー問題を解決するのによく使われる手法は、アトミック変数をvolatile修飾することだという。しかし、これでは問題のない好ましい最適化まで抑制されてしまう。コンパイラーがそこまで過剰に最適化を行わない場合はパフォーマンスの損失につながる。

論文筆者のHans Boehmの考えでは、コンパイラーはアトミック変数を過剰に最適化するべきではない。属性などで過剰に最適化させる方法を提供すべき。としている。

過剰な最適化は、shared_ptrの例のようにとてもよいこともあれば、プログレスバーの例のようにとても悪いこともある。コンパイラーにとって、良い悪いは判断できない。判断できないのであれば、どちらがわにも倒さずにコードをそのままにしておくべきだ。

とはいえ、過剰な最適化の利点はあるので、そのための方法を提供すべきだ。

論文は最後に、規格の文面に追記する注釈案を載せている。

P0063R0: C++17 should refer to C11 instead of C99

C++規格が参照するC規格をC99からC11に引き上げる提案。

単に参照先を買えるだけではない変更も考察されている。シグナルハンドラー内ではthread_local変数は使えない。新たに追加されたCヘッダーに対してはC++規格からは参照しない。Atomicsについては、_Atomic(T)がatomic<T>になっていると嬉しい。C言語に新しく追加された有益な機能のC++版がないなど。

[PDF] P0065R0: Movable initializer lists, rev. 2

initalizer_list<T>のbegin/endの戻り値の型は、const T *である。そのため要素をムーブできない。この提案は、非constなT *を返すbegin/endを持ったown_initializer_list<T>を提案している。

初期化リストから推定される型Tが、非トリビアルなムーブコンストラクターを持っている場合は、型はown_initializer_list<T>になる。そうでない場合は、initializer_list<T>になる

// std::initializer_list<int>
auto x = { 1, 2, 3 } ; 
// std::own_initializer_list<std::string>
auto y = { std::string("hello") } ;

[PDf] P0066R0: Accessors and views with lifetime extension

一時オブジェクトをリファレンスに束縛すると、一時オブジェクトの寿命がリファレンスの存在期間中延長されるが、現行の規程は問題が多い。

std::string const & f( std::string const & ref ) { return ref ; }

int main()
{
    // 寿命が延長されない。
    auto const & ref = f( std::string("hello") ) ;
}

initializer_listだけはこのような一時オブジェクトの寿命延長をコンパイラーマジックによって行っている。

一時オブジェクトの寿命延長を関数やクラスのサブオブジェクトを経由してもユーザーが行えるようにする寿命修飾子を提案しているのだが、どう考えても非現実的な机上の空論のクソ機能にしか思えない。ABIに変更をもたらすのもクソだ。真面目に読んで損をした。

P0067R0: Elementary string conversions

整数と文字列、浮動小数点数と文字列間の相互変換のみを行うライブラリ、to_string, from_stringの提案。

JSONやXMLなどのテキストベースのフォーマット利用が拡大している現代では、数値と文字列間の高速な変換が必要である。変換はinternationalizationを行う必要はない。

そのためには、以下の要件を満たす必要がある。

既存の標準ライブラリで提供されている方法はすべて、localeに依存している。

また、変換処理は、教科書通りの実装では速くない。高速な実装方法が多数ある。

この提案では、極めて原始的なインターフェースを採用している。char *型のイテレーターベースのインターフェースだ。数値を文字列に変換するにはto_string、文字列を数値に変換するにはfrom_stringを使う

char * to_string(char * begin, char * end, T value, その他の引数) ;

T型の数値valueを文字列に変換したものが、[begin, end)に書き込まれる。戻り値は書き込んだ最後の要素の一つ次のポインターになる。文字列を書き込むスペースが足りない場合、endが返される。スペースが足りない場合に書き込まれる内容は未規定である。

整数型は以下のような関数になる。

char * to_string(char * begin, char * end, T value, int base = 10);

baseの範囲は2から36までで、10から36までの数値はaからzで表現される。valueが負数の場合、マイナス符号が先頭に書き込まれる。

浮動小数点数型は、変換する文字列のフォーマットごとに、以下のような2つの関数が提供されている

31415e-4のようなフォーマットの文字列に変換

char * to_string(char * begin, char * end, T value);

valueが負数の場合はマイナス符号が先頭に書き込まれる。文字列は10進数で、整数部が少なくともひと桁あり、オプショナルで小文字eに続いてexponentが記述される。表現は最小文字数かつ、from_stringによって元のvalueが正確に復元できるものとなる。valueが無限大の場合、infか-infとなる。NaNの場合、nanか-nanとなる。

3.1415のようなフォーマットの文字列に変換

char * to_string(char * begin, char * end, T value, int precision);

小数部の桁数はprecisionになる。

文字列を数値に変換するfrom_stringは以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec, 残りの引数 );

戻り値はパターンにマッチしない最初の文字へのポインター。

整数型に変換する場合は以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec, int base = 10);

浮動小数点数型に変換する場合は以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec);

文字列に変換する際のinfやnanも正しく認識される。

[PDF] P0068: Proposal of [[unused]], [[nodiscard]] and [[fallthrough]] attributes

タイトル通り、[[unused]], [[nodiscard]], [[fallthrough]] attributeの提案。

[[unused]]

[[unused]]が指定されたエンティティは、何らかの理由で使われていないように見えるという意味を持つ。これによって、実装による未使用警告を抑制できる。

int x ; // 警告、xは未使用
[[unused]] int x ; // 警告が抑制される

[[nodiscard]]

[[nodiscard]]が指定された関数は、戻り値がdiscardされてはならないことを意味する。戻り値が捨てられた場合、警告が出る。

[[nodiscard]] bool do_something_that_may_fail() ;

int main()
{
    do_something_that_may_fail() ; // 警告、戻り値が捨てられてい

    // OK
    if ( do_something_that_may_fail() )
    {
    }
    else
    {
    }
}

[[nodiscard]]が型に指定された場合、その型を戻り値の型に使う関数が[[nodiscard]]指定される。


[[ nodiscard]] class error_code { ... } ;

// 関数fは[[nodiscard]]指定される
error_code f() ;

[[fallthrough]]

[[fallthrough]]は文のように記述される。これをfallthrough文という。

[[fallthrough]] ;

[[fallthrough]]はcaseラベルの直前に書く。これによって、複数のケースを突き抜けたコードに対しての警告を抑制できる。

switch( x )
{
case 0 :
    do_something() ;
    // 警告、fallthrough
case 1 ;
    do_something() ;
    [[fallthrough]]
    // 警告なし
case 2 ;
    do_something() ;

} 

いい提案だ。

[PDF] P0069R0: A C++ Compiler for Heterogeneous Computing

GPU用に現在提案されているGPUのサポートも視野に入れた機能をC++コンパイラーに実装した経験の報告書。

開発したのコンパイラー、HCC(Heterogeneous C++ Compiler)は、Clang/LLVMベースのコンパイラーだ。HCCはホストプロセッサー用のコード(x86)とアクセラレーター用のコード(AMD GPU/HSA)を同時に生成する。HCC自体は公開されていないようだ。

問題となるのは、コードのどこの部分をどちらのプロセッサー用のコードで生成するかだ。すべてのコードをすべてのプロセッサー向けにコード生成を行うと、コンパイル時間が増大する上に、プロセッサーごとの特性もあるので、逆に遅くなる。

C++提案では、特定の領域に対して機能を制限してコンパイラーにヒントを与える機能があるが、これはあまり役に立たなかったと報告している。

HCCがかわりに開発したのは、プログラマーがコンパイラーがコード中の並列部分を指示できる機能云々

なんだかOpenMPのようだ。

モダンなGPUはC++の機能のほとんどを実行できるようになった。たとえば、間接的な関数呼び出し、バイト単位のメモリアクセス、基本型、アライメントされていないメモリへのアクセス、スタック、gotoなどだ。

HCCはC++のほとんどの機能をアクセラレーター向けにコード生成できる。例外はtry/catchブロックなどだ。try/catchブロックの実装は技術的に可能であるが、現在のところ実装していない。論文著者は、アクセラレーター向けにC++のサブセットを定義する必要はないと報告している。

ドワンゴ広告

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

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

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