2014-11-20

2014-10-pre-Urbanaのレビュー: M4190-N4199

N4190: Removing auto_ptr, random_shuffle(), And Old <functional> Stuff

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になるのではないかと論文筆者は記述している。同感だ。

N4191: Folding expressions

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のどちらか片方だけがパラメーターパックでなければならない。

これは興味深い提案だ。

N4192: C++ Standard Core Language Active Issues

C++のコア言語で既知の問題集。

N4193: C++ Standard Core Language Defect Reports and Accepted Issues

C++のコア言語で解決済みの問題集

N4194: C++ Standard Core Language Closed Issues

C++のコア言語で、議論の結果、何も対応しないと結論された問題集。

[PDF注意] N4195: std::synchronic<T>

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に匹敵するパフォーマンスを得られる。

N4196: Attributes for namespaces and enumerators

[[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文字の値をリテラルで記述できるようになった。

N4198: Allow constant evaluation for all non-type template arguments

非型テンプレート仮引数に渡せるテンプレート実引数には、様々な制限がある。ただし、その制限は、現状にあっていない。ポインター、リファレンス、メンバーへのポインターが特にひどい。

ポインター型は、静的ストレージ上のオブジェクト、もしくはリンケージを持つ関数で、その文法は&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は同じ型だろうか。この問題を避けるために、完全なオブジェクトでなければならない。

N4199: Minutes of Sept. 4-5, 2014 SG1 meeting in Redmond, WA

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

2 comments:

Anonymous said...

> #includeにもdeprecatedを指定したい
ヘッダファイルという仕組み自体を過去のものにしたいんでしょうなあ

Anonymous said...

N4191を見てると、Valarrayェ・・・ってなるんですが。確かに便利は便利ですね。Valarrayのほうを一回解体して再設計するレベルまで来てるんじゃないでしょうかね。思想通りSIMDに動的に展開されれば恩恵はでかいでしょうし。
それと、未定義動作の時にデフォルトの返り値を定義するんだったら、未定義の変数も0なり-1なりで初期化の強制をしてほしいです。
えーっと、折角C++にもSleep関数が入ったんですから、それを活用してほしいと切に思っています。
std::this_thread::sleep_for
これですね。実は自分には待望の機能でしかもnanosec単位で寝れるじゃないですか。適切に使ってスレッドを解放してください。OS依存のスリープ関数しか存在しなくて泣いてた時を思い出しますよ。なんでこれを最初から標準に入れなかったのか・・・。ブツブツ。
タイトループはディプリケーテッドにしましょう。