2019-01-07

2018-11のC++ドラフトの主要な変更

N4792

C++20のドラフトが更新された。今回も強めの変更が入っている。

まずconstexprが大幅に強化された。

p1002r1.pdf

Allowing dynamic_cast, polymorphic typeid in Constant Expressions

C++20での最終的な目標は、std::vectorやstd::stringをconstexpr対応させることだ。そのために従来ならば実行時処理であった様々な機能がconstexprに対応している。今回の変更では、try/catchブロックやdynamic_cast/typeidがconstexprに対応した。また、unionの有効なメンバーの変更もconstexprに対応した。

try/catchブロックはコンパイル時評価される場合、単に無視される。

dyanmic_cast/typeidは本当にconstexprに対応する。すでにvirtual関数呼び出しがconstexprに対応していることを考えるとこれは当然だ。結果的に、コンパイル時に動的ポリモルフィズムが可能になるということだ。

最終的な目標は、new/deleteをconstexpr対応させることだ。そうすれば、std::vectorやstd::stringは、なにもせずともそのままconstexpr対応する。今年中に入る見込みだ。

std::is_constant_evaluated

また、constexpr関数がコンパイル時に評価されているかどうかを確かめるstd::is_constant_evaluated()関数が追加された。これによって、constexprが本当にコンパイル時に評価されている場合に条件分岐できるようになった。

constexpr int const_sqrt( double d )
{
    if constexpr( std::is_constant_evaluated() )
    {
        // コンパイル時評価
        // 定数式にできるsqrt実装
    }
    else
    {
        // 実行時評価
        // 実行時に効率のいいsqrt実装
    }
}

constexpr関数はコンパイル時にも実行時にも評価される。


constexpr int f( int x ) { return x ; }

int main()
{
    int x = f(0) ; // コンパイル時評価
    f(x) ; // 実行時評価
}

しかし、本当にコンパイル時にだけ評価されてほしい関数を書きたい。このために、consteval関数が追加された。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r3.html

consteval関数はコンパイル時にしか評価されない。実行時評価しようとするとill-formedになる。


consteval int f( int x ) { return x ; }

int main()
{
    // OK、コンパイル時評価
    int x = f(0) ; 
    // ill-formed、実行時評価が必要
    f(x) ;
}

これにより本当にコンパイル時にしか評価されない関数を書ける。

P0907R4: Signed Integers are Two’s Complement

C++20では、符号付き整数型は2の補数で表現されることが保証された。もはや2の補数表現を用いていないアーキテクチャは実質死滅したので、これは正しい変更だ。これまではatomicに限り2の補数表現が保証されていたが、すべての符号付き整数型が2の補数表現である現実的な規格になった。

char8_t: A type for UTF-8 characters and strings (Revision 6)

UTF-8文字型のchar8_tが入った。理想的な変更が行われた。

char8_tは基本型である。UTF-8文字と文字列はchar型からchar8_t型に変更される。暗黙の型変換は存在しない。ユーザー定義リテラルにchar8_t版が追加される。

標準ライブラリもchar8_tに対応する。std::basic_stringのchar8_tに対するtypedef名として、std::u8stringが追加される。その他のライブラリもchar8_tとstd::u8stringの存在を前提としたC++11の時点で汝あるべき姿に戻る。

Yet another approach for constrained declarations

コンセプト名をプレイスホルダー名(auto/decltype(auto))が使える文脈ならばどこでも使えるようにする。

void f(Sortable auto x);
Sortable auto f();     
Sortable auto x = f();  
template <Sortable auto N> void f();

これをすべて組み合わせると以下のように書ける。

template <Sortable auto N> Sortable auto f(Sortable auto x)
{
    Sortable auto y = init;
}

非制約版は以下のようになる。

template <auto N> auto f(auto x)
{
    auto y = init;
}

なぜこのように書きたいかというと、変数や戻り値の型推定の場合にも型制約は書きたいためだ。


// 具体的な型は知らないが
// Sortable制約を満たしているべき
Sortable auto x = f() ;

そのためにコンセプト名による制約を書けるようにした。

decltype(auto)も使える。

auto f() -> Sortable decltype(auto)
{
    return g() ;
}

Nested Inline Namespaces

C++17ではネストされたnamespaceが簡単に書けるようになった。

// C++14まで
namespace A {
    namespace B {
        namespace C {
        }
    }
}

// C++17
namespace A::B::C {
}

ただし、これはinline namespaceに対応していない。そのため、C++17をもってしても以下のように無骨に書かなければならない。

namespace A {
    inline namespace B {
        namespace C {
        }
    }
}

C++20ではnested namespaceがinline namespaceに対応した。

namespace A::inline B::C {
}

Merge the Ranges TS - p0896r4.pdf

とうとうOne Range提案がドラフト入りした。One Range提案はRange TSから派生したRange提案で、J.R.R.Tolkienの指輪物語へのポップカルチャーリファレンスだ。その冒頭は以下のように始まっている。

みっつの提案は空の下なるViewらのために
ななつの提案は岩の館のライブラリ拡張ワーキンググループに
ここのつの提案は死ぬべき定めのRange TSに
ひとつの提案は暗黒の玉座のライブラリワーキンググループのために
標準のいますジュネーブの地に

ひとつの提案はすべてをrange::mergeし、ひとつの提案はすべてをrange::findし
ひとつの提案はすべてをまとめ、namespace rangesの元に束縛する
標準のいますジュネーブの地に

Rangeはとても便利だ。例えばvectorの中身を逆順にたどりたいとする。もちろんRange-based forは使いたい。

std::vector<int> v ;
for ( auto & e : v | reverse )
    ... ;

問題はここで終わらない。vectorの中身を逆順にたどりたいついでに、値が100以下の要素だけたどりたい。

for ( auto &amp; e : v
    | reverse
    | filter( []( auto & e ){ return e < 100 ; } )
)
    ... ;

問題はまだまだ終わらない。上の要素を5個だけ処理したい。

for ( auto & e : v
        | reverse
        | filter( []( auto & e ){ return e < 100 ; } )
        | take(5)
)
    ... ;

にわかに0から99までのインデックスループがしたくなった。

for ( auto i : iota(0, 100) )
    ... ;

奇数だけ処理したくなった。


auto odd = []( auto n ) { return n %2 == 1 ; } ;
for ( auto i : iota(1) | filter( odd ) )
    ... ;

最初の100個の奇数だけ処理したくなった。


auto odd = []( auto n ) { return n %2 == 1 ; } ;
for ( auto i : iota(1) | filter( odd ) | take(100) )
    ... ;

これでC++20プログラマーはHaskellに近づくことができる。

あなたが道端を歩いていたところ、急に老婆が近寄ってきて言った。

「すまんがお若いの、各桁の合計が40に等しい最初の自然数はなんじゃろうか」

Learn You a Haskell for Great Good

auto rule = [](auto n) {
    auto s = std::to_string(n) ;
    decltype(n) sum = 0 ;
    for ( auto digit : s )
        sum += digit - '0' ;
    return sum == 40 ;
} ;

std::cout << *begin( iota(1) | filter( rule ) ) ;

空白で区切られた単語をstd::vectorに格納したくなった。

std::string sentence = "quick brown fox" ;
std::vector<std::string> words ;
std::copy( sentence | split( ' ' ), std::back_inserter( words ) ) ;
// vは{"quick", "brown", "fox"}

区切った単語の各文字を雑に処理したくなった。

for ( char c : words | join )
    std::cout << c ;
// 出力は"quickbrownfox"

あとはC++にモナドが入るのを待つだけだ。

C++20はコンセプトとレンジのおかげで圧倒的に使いやすくなる。

No comments: