2019-01-28

P1337R0: 標準ライブラリへのエイリアスを追加してC++を復活させる提案

p1337r0.pdf

素晴らしい提案が2019年4月1日付けの文書でC++標準化委員会に上がっている。

標準ライブラリへのエイリアスを追加してC++を復活させる提案

背景

Node.jsやRuby on Railsのような高パフォーマンスでスケーラブルな開発ツールの興隆を受けて、C++は市場を失いつつある。10倍プログラマーと呼ばれるような最高の開発者達は、Web 3.0アプリケーションとシステムを構築するのにもはやC++を使わない。かつてC++は当然使うべき言語であったが、近年落ちぶれている。もしこのまま何もしなければ、C++は言語として絶滅するだろう。GoogleのAbseilチームはC++の危難を救うために立ち上がった。これによりすべてのプログラマー、特に最高のプログラマーですら、C++を使うことができるようになるだろう。

1337提案

我々Abseilチームはかく提案する。C++2aより先、標準ライブラリに第二の名前空間を追加する。名前空間は"57d::"で、"57d::"のすべてのシンボルは名前空間"std::"へのエイリアスとなる。"57d::"のエイリアスは"std::"のそれぞれの名前から標準1337スピークマッピングによって変換される。"std::"名前空間にマッピングされない"57d::"名前空間内の名前は存在しない。

1337スピークとは何か?

1337スピークとは文字の変換のことであり、特定のASCII文字が対応する数字によって置き換えられたものだ。これはインターネット上のハッカーと天才プログラマーがコードを話すのに自然に用いている言葉だ。

  1. "std::aligned_storage"は"57d::4116n3d_570r463"とエイリアスされる
  2. "std::index_sequence_for"は"57d::1nd3x_539u3nc3_f0r"とエイリアスされる
  3. "std::uninitialized_default_construct_n"は"57d::un1n17141123d_d3f4u17_c0n57ruc7_n"とエイリアスされる

先例

名前空間 "std2::"

活用例

"<windows.h>"を#includeすると"min(...)"と"max(...)"という関数風マクロでプログラムが汚染されるのはC++において周知の事実である。"-DNOMINMAX"が標準の解決法として確立している。この提案を採用すれば、このビルドフラグは必要がなくなる。C++開発者は"57d::m1n(...)"と"57d::m4x(...)"と恐れることなく書けるためである。

ODRとU8

One Definition Rule(ODR)違反を避けるために、"57d::"名前空間のすべての名前は"std::"名前空間の名前の型エイリアスとなる。ただし、名前空間を超えて暗黙にせよ明示的にせよ型のインスタンスの変換を行うのは未定義の挙動(U8)である。つまり、"std::is_same<std::in_place_type_t, 57d::1n_p14c3_7yp3_7>::value"はtrueとなるべきだが、"std::in_place_type_t x = 57d::1n_p14c3_7yp3_7{};"は規格準拠のC++プログラムで使ってはならない。このことによる問題はきわめてまれである。なぜならば10倍プログラマーは極めて慎重であるし、"57d::"名前空間を使えるのは10倍プログラマーだけだからだ。

シンボルの先頭の数字文字

C++17ではシンボルの先頭は数字文字で始まってはならない。これはC++2aでモジュールを入れることにより解決される。

提案するマッピング

[訳注:原文参照。a/Aは4、b/Bは8、c/Cはc/Cなど]

将来の拡張

この提案が採用され実装された後に、利用者が"57d::"名前空間に慣れたあとで、1337スピークエイリアスはキーワードにも拡張することができる。もう"co_*"で悩む必要はない。なぜならば4w417(await), y131d(yield), r37urn(return)キーワードが使えるからである。

議論事項

  • 名前マングリングはbase64で行うべきか?
  • 非修飾P051X(POSIX)の名前は10倍Cプログラマーのために非修飾1337スピークエイリアスにするべきか?

リファレンス

[1] HTML ASCII Reference

[2] P0180: Reserve a New Library Namespace Future Standardization

[3] C++ Logo ASCII Art[原文参照]

2019-01-22

C++20のRangeライブラリの強力な機能、プロジェクション

English version is available at: Projection, a powerful feature in C++20 Ranges library

C++20のRangesライブラリにはプロジェクションという強力な機能が追加された。

例えば、人間を表現するクラスがあったとして、

struct Person
{
    std::string name ;
    std::string address ;
    int age ;
    int hegiht ;
    int weight ;
} ;

そのvectorであるpersonsがあるとして、

std::vector<Person> persons ;

このpersonsを特定のデータメンバーでソートしたいとする。

これは比較関数を自分で書くことで可能になる。

std::sort( persons, []( auto & a, auto & b ) { return a.age < b.age ; } ) ;

しかしこんなコードは書きたくない。ボイラープレートにもほどがあるし、コンパイラーは間違えてもエラーも警告も出してくれない。例えば、

// 比較演算子を間違えている
std::sort( persons, []( auto & a, auto & b ) { return a.age > b.age ; } ) ;

あるいは、

// 2つの引数を比較していない
// コンパイラーは警告すらしてくれない
std::sort( persons, []( auto & a, auto & b ) { return a.age < a.age ; } ) ;

さらには、

// 比較するメンバーを間違えている
// 型が同じなのでコンパイラーは警告してくれない。
std::sort( persons, []( auto & a, auto & b ) { return a.age < b.height ; } ) ;

C++コンパイラーは上記のコードにエラーも警告も出さない。コードは完璧に合法で間違っていないからだ。コンパイラーはプログラマーの不文律の意図を推測してくれたりはしない。最近流行りの機械学習2.0とやらでも人間様の不文律の意図を判定しようなどということはできないだろう。たぶん。

C++20のレンジライブラリを使えばこの問題はプロジェクションという機能で解決できる。単にranges::lessとデータメンバーへのポインターを渡すと動く。

std::ranges::sort( persons, std::ranges::less, &Person::age ) ;

なぜ動くのか。プロジェクションなしのranges::sortはだいたい以下のようになっている

auto sort = []( auto && range, auto comp )
{
    // ...
    // i, j はイテレーター
    // 2つの要素を比較する
    if ( comp( *i, *j ) )
    // ...
} ;

ranges::sortにはプロジェクションという追加の引数がある。

auto sort = []( auto && range, auto comp, auto proj ) ...

これは以下のように動く。

auto sort = []( auto && range, auto comp, auto proj )
{
    // ...
    if ( comp( std::invoke( proj, *i), std::invoke( proj, *j ) ) )
    // ...
}

std::invokeというのは野暮ったい関数呼び出しだ。std::invoke( f, args... )は、fが関数の場合、f( args ... )と同じだ。そういう意味では、上記のコードは以下と等しい。

if ( comp( proj(*i), proj(*j) ) )

もし、fがデータメンバーへのポインターであり、args... が引数1つでクラス型のオブジェクトの場合、std::invoke( f, a )はa.*fに等しい。

if ( comp( (*i).*proj, (*j).*proj ) )

この場合で、i, jのvalue_typeがPersonで、projが&Person::ageの場合、以下のようになるわけだ。

if ( comp ( i->age, j->age ) )

なので動く。

std::invokeを使っているので、引数を取らないメンバー関数へのポインターを渡しても動く。

class Person
{
    int age ;
public :
    int get_age() const noexcept { return age ; } ;
} ;

int main()
{
    std::vector<Person> persons ;
    std::ranges::sort( persons, std::ranges::less, &Person::get_age ) ;
}

普通に関数オブジェクトをプロジェクションとして渡すこともできる。

std::ranges::sort( persons, std::ranages::less, []( auto && n ) { return n.age ; } ) ;

このコードは少々長ったらしいが、C++17時代の古臭いコードよりはマシだ。というのもプロジェクション関数は1つの引数をどのようにプロジェクトするか、ということにしか責任を負わないので、上で上げたような些細なバグを引き起こすことはない。

他にはどのようなアルゴリズムがプロジェクションをサポートしているのか。だいたいのユーザーからの関数オブジェクトを取るアルゴリズムはプロジェクション関数オブジェクトを最後の引数に取る。


all_of( range, pred, proj ) ;
for_each( range, function, proj ) ;

ところで、std::ranges::transformもプロジェクションをサポートしている。

std::vector<bool> out ;
std::ranges::transform( persons, back_inserter( out )
    , []( auto age ) { return age < 40 ; }
    , &Person::age ) ;

このコードはpersonsからPersonの値をそれぞれ取り、データメンバーのageにプロジェクトし、条件付きでboolにトランスフォームし、vectorのoutにpush_backしている。

transformが受け取る関数オブジェクトはプロジェクションと同じなので、これは冗長のように思われるが、transformもプロジェクションをサポートすることで一貫性があるのと、トランスフォーム関数とプロジェクション関数をわざわざユーザー側で合成しなくてもすむようになる。

ところでtransfromといえば、std::ranges::view::transform_viewも関数オブジェクトはstd::invoke経由で呼んでいる。これはプロジェクションというわけではないが、プロジェクションと同じように動く。

for ( auto age : persons | transform( &Person::age ) )
    std::cout << age << '\n' ; 

このコードはレンジとしてpresonsを取り、データメンバーへのポインターを渡したtransfrom_viewを適用する。transform_viewはstd::invokeを使っているのでこれは動く。変数auto ageにはレンジ内のPersonの値それぞれのageが入る。

この記事はP1252提案が入ることを前提にしている。

Merge the Ranges TS - p1252r0.pdf

Projection, a powerful feature in C++20 Ranges library

I'm Ryou Ezoe. Today, I'm going to write about the projection, a powerful feature in C++20 Ranges library.

Suppose, you have a class that represents a person:

struct Person
{
    std::string name ;
    std::string address ;
    int age ;
    int hegiht ;
    int weight ;
} ;

And a vector of persons.

std::vector<Person> persons ;

Naturally, you want to sort persons by a specific data member.

How can we do that? You can write your own compare function.

std::sort( persons, []( auto & a, auto & b ) { return a.age < b.age ; } ) ;

I don't want to write this. Not just for itslong boilerplate code, but the compiler can't catch the obvious bugs like this.

// using a wrong comparison operator 
std::sort( persons, []( auto & a, auto & b ) { return a.age > b.age ; } ) ;

Or this.

// It doens't compare the two parameters.
// Compiler don't warn it because it's perfectly well-formed code.
std::sort( persons, []( auto & a, auto & b ) { return a.age < a.age ; } ) ;

Or this.

// comparing wrong data members.
// the types are same so compiler don't warn it.
std::sort( persons, []( auto & a, auto & b ) { return a.age < b.height ; } ) ;

The C++ compiler cannot warn these codes because it's perfectly well-formed code. The compiler can't guess the programmer's unwritten intention and the last time I checked, nobody seriously researched on using trending machine learning 2.0 based solution which can guess the unwritten intention.

The C++20 Ranges got you covered on this problem with the projection. You can simply pass the ranges::less and a pointer to the data member as arguments and it just works.

std::ranges::sort( persons, std::ranges::less, &Person::age ) ;

Why does it work? the ranges::sort without projection works like this.

auto sort = []( auto && range, auto comp )
{
    // ...
    // i, j are iteretors
    // compare two elements for ordering
    if ( comp( *i, *j ) )
    // ...
} ;

But ranges::sort has a extra parameter for projection.

auto sort = []( auto && range, auto comp, auto proj ) ...

And it works like this.

auto sort = []( auto && range, auto comp, auto proj )
{
    // ...
    if ( comp( std::invoke( proj, *i), std::invoke( proj, *j ) ) )
    // ...
}

std::invoke is an ugly version of function call. std::invoke( f, args.. ) is equivalent to f( args ... ) if the f is function. In that sense, above code is equivalent of

if ( comp( proj(*i), proj(*j) ) )

But if the f is a pointer to a data member, and args... has exactly one argument which is a object of class type, std::invoke( f, a ) is equivalent to a.*f ;


if ( comp( (*i).*proj, (*j).*proj ) )

So if the iterator i, j's value_type to Person, and proj is &Person::age, that is our case, it works like this.

if ( comp ( i->age, j->age ) )

Thus it just works.

Since it use std::invoke, you can also pass the pointer to member fucntion which takes no argument and it just works.

class Person
{
    int age ;
public :
    int get_age() const noexcept { return age ; } ;
} ;

int main()
{
    std::vector<Person> persons ;
    std::ranges::sort( persons, std::ranges::less, &Person::get_age ) ;
}

You can also pass function object too.

std::ranges::sort( persons, std::ranages::less, []( auto && n ) { return n.age ; } ) ;

This code looks like boilerplate too you. But it's actually better than C++17 era code. Because projection function only deal with one parameter and how to project that parameter. You don't need to write the rest of boilerplate code so you are immune from above typical problems.

So, what other algorithms support the projection? Well, most of them. Those which take a function object from user also take the projection function object in the last parameter.


all_of( range, pred, proj ) ;
for_each( range, function, proj ) ;

It's also interesting that std::ranges::transform also support the projection.

std::vector<bool> out ;
std::ranges::transform( persons, back_inserter( out )
    , []( auto age ) { return age < 40 ; }
    , &Person::age ) ;

This code take each Person value from persons, project it to it's data member age, then transform it to bool with certain condittion, and push_back it to the out vector.

Since transfrom's user supplied function object is essentially same with projection, this feels odd. But it's good for consistency and you don't need to precombine the function object and projection by yourself.

Speaking of transform, std::ranges::view::transform_view call function with std::invoke too. Although this isn't a projection in strictly speaking, but it works like a projection.

for ( auto age : persons | transform( &Person::age ) )
    std::cout << age << '\n' ; 

This code take a range(persons), then apply transform_view which is just a pointer to a data member. Since transform_view call function by std::invoke, it just works. and variable auto age take each age value of Person object inside the range.

2019-01-21

大炎上の煙くすぶる警察によるTポイントカード照会

Tカード情報令状なく捜査に提供 規約明記せず、当局は保秘(共同通信) - Yahoo!ニュース

Tポイントカードが令状なしの警察の照会に応じていて、その内容が購入歴機を含み、かつ、本人に知らせなかったという事態が明らかになっている。

これはGPS裁判と似た危険性を感じる。そして、おそらく業界全体に飛び火して大炎上するだろう。

この問題は、まず令状を取っていないということと、照会に応じた事実が本人に知らされていないという点で極めて危険だ。なぜならば、照会に応じた事実が本人に知らされていない場合、公平な裁判ができなくなるからだ。

たとえばGPS裁判では、警察が秘密裏に被疑者の車にGPSを受信して記録する装置を取り付けている。これによって警察は車の極めて正確な位置情報を得て、その位置情報によって別の証拠を固めて裁判の証拠とした。これは違法な証拠の収集であり、そのような違法に収集された証拠とそれに付随して得られた証拠は無効な証拠になる。

Tポイントカードも同じ問題をもっている。本人に照会に応じた事実を知らせず裁判になり、Tポイントカードの購入履歴自体は証拠として提出せず、Tポイントカードの購入履歴を使って間接的に得られた別の証拠を提示された場合、本人はTポイントカードの購入履歴が証拠になっているという秘密の事実を知らないまま裁判をしなければならない。Tポイントカードの購入履歴を知らないはずなのに極めて正確な購入履歴や店舗利用情報に基づいた証拠が出された場合、被疑者は正当な裁判を受けることができない。なぜならばその提出されていない秘密の証拠に反論できないからだ。したがってそのような証拠は違法となるべきである。

Tポイントカードがしていることは犯罪捜査を助けているのではなく、むしろ犯罪者を助けているのだ。なぜならば違法に収集された証拠は当然無効になるので、証拠として使えなくなるからだ。違法に収集された証拠が無効になるというのは当然の話で、これを認めるともたらす害悪のほうが大きいからだ。

この令状なしの警察による照会に気軽に応じるという行為はいずれ大炎上するだろうし、そのとき国内のほぼすべてのWebサービスやポイント制度、電子マネーは無傷ではいられないだろう。

炎上の被害を今から抑えるために、今まで応じてきた警察からの照会情報をすべて開示し、本人に通知すべきだ。そうすれば、少なくとも照会が行われたという事実が本人に伝わるので、公平な裁判は行えることになる。

2019-01-13

Possibility of writing English C++ textbooks

I am Ryou Ezoe. I made my living by explaining the very latest C++ features in Japanese. I wrote two C++ books.

"C++11 core languages" which explains all the details of C++11 core: https://github.com/EzoeRyou/cpp-book

"Ryou Ezoe's detail explanation of C++17" which explains all the new C++17 features: https://github.com/EzoeRyou/cpp17book

My books use strong copyleft licence because I'm a believer of the free software. A concept defined by RMS. My last C++17 book was GPLv3 because I convinced the publisher and editor to release the source code of the book under the GPLv3 license. The github repository contains the markdown source file I wrote and the very same tex source code the publisher used to layout and print my book.

I'm writing in Japanese because it's the native language for me. I've never thought writing in English because: 1) English is not my native languages so my English is not great. 2) there are so many talanted native English speakers so I have no hope of competing with them.

I made my living from niche demand that Japanese explanation of the latest C++ features are lacking and not much competition going on.

Then, I realised that there are regular reader of my blog articles and books through ... machine translation!

This fact surprise me. grammatically, Japanese and English are way too much different so the machine translation between Japanese and English doesn't work well.

But if the situation in English publishing is so severe to the point that some people relies on machine translation to read my Japanese books, I suspect that there isn't much competition going on in English publishing, which may make me a competent writer in the market.

So I wrote an short English article for the overview of C++20 Range view.

The overview of C++20 Range view

It turns out writing English isn't that hard. There are minor grammatical errors here and there. But these errrors can be fixed by proofreading of natives. As for the writing speed, I spend more time on studying the new knowledge than actually writing the explanation, so English doesn't slow down the writing speed. For that, I think I can manage to write both Japanese and English in parallel for my next C++20 book.

So what do you think? Feel free to send your opinions. I'm available at email boostcpp@gmail.com or Twitter @EzoeRyou.

2019-01-10

The overview of C++20 Range view

The latest C++ draft as of this writing incorporated One Range proposal.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4791.pdf

So what is the Range anyway? A C++ Standard Comittee member, Eric Nibeler, summrised it well.

Eric Niebler – Eric Niebler

Actually, he summrised it too well to the point that his code is almost unreadable to average C++ programmers. It's practically useless. So this article serve as the quick tutorial for the Range view.

The Range is a beefed up Iterator. Just like the Iterator was modeled after the pointer, the Range was modeled after the pair of iterators [begin, end).

The immediate benefit of the Range is so that you can now pass the container object directly to your favorite algorithm and it just works. Because the Containers satisfy the concept of Range.

std::vector v = {1,2,3,4,5} ;
std::for_each( v,
[]( auto x )
{ std::cout << x ; }
) ;

Or, as its name suggests, Range-based for.

for ( auto x : v )
    std::cout << x ;

"But Range-based for existed from C++11! It was already there!" You might say. Well C++11's Range-based for didn't have the power it was originally intended. Because the Concept didn't make it to the C++11. Those good-for-nothing standard commitee members couldn't agreed on whether the concept map shall be implicitly generated or not. The same bunch of idiots who can't accept char8_t until C++20, blubbing: "but char is the suitable type for representing the contagious raw bytes" blah blah blah.

Now the Concept is there, we can finally emblace the full power of Range-based for.

The Range library has the View. A View is a range adaptor. Views behaves like a range, so it is a range. Sort of.

Now suppose we want to iterate over the above vector, but backwards.

C++17 Range-based for is useless for this very very simple task. We have to fallback to the C++98 era reverse iterator.

std::for_each( rbegin(v), rend(v),
    []( auto i )
    {
        std::cout << i ;
    } ) ;

At least, we have lambda expression. But it doesn't save much of the ugliness.

In C++20, you can simply use reverse_view.

for ( auto i : v | reverse )
    std::cout << i ;

Yes. That's it. This very simple, and actually readable code prints "54321". Don't worry. There is absolutely no performance penalty at all! C++ is better than Haskell.

Now, suppose that, you want to iterate over 1 to n. The n is a runtime value. Creating a vector object is inefficient.

std::vector<int> v ;
int n ;
std::cin >> n ;
for ( int i = 1 ; i != n+1 ; ++i ) 
    v.push_back(i) ;

Fortunately, C++20 has iota_view. iota(a, b) create a range of integers incrementing in range \(a \leq; n < b\) .

int n = 10 ;
for ( auto i : iota( 1, n ) | reverse )
    std::cout << i ;

Now, this code prints "987654321".

There are a lot of numbers. We want to get rid of the even numbers so that we only deal with odd numbers. We can use filter_view.

for ( auto i : iota(1, 10)
    | filter( [](auto x){ return x % 2 == 1 ; }
)
    std::cout << i ;

It prints "13579".

filter(rule) drop elements x where function rule(x) returns false.

Now let's suppose that we have a function is_prime(n) which returns true if n is probably a prime number. I don't go into details how we can implement is_prime. If you want to know, search for Miller–Rabin.

This code prints all the prime between number 1-1000.

for ( auto i : iota(1, 1001)
    | filter( is_prime )
)
    std::cout << i << ', ' ;

This works. But what if you want the first 10 prime numbers? We can use take_view. take(n) takes n elements from the range.

for ( auto i : iota(1)
    | filter( is_prime )
    | take ( 10 )
)
    std::cout << i << ', ' ;

It prints "2, 3, 5, 7, 11, 13, 17, 19, 23, 29, "

You may notice that above code pass only one argument to iota_view. iota(n) create a range start from n and increment indefinitely. That means if you wrote like this:

for ( auto i : iota(1) )
    std::cout << i << '\n' ;

It prints numbers until it overflows and still continues printing overflowed numbers. It's a inifinite loop. It never stops.

take_view can stop the execution such inifinte loop because it only take n elements from the previous range.

for ( auto i : iota(1) | take(10) )
    std::cout << i '\n' ;

This code prints 1 to 10 and stop the loop.

We can use iota_view to write index loop. Suppose we want to iterate integer from 0 to 100. Traditionally, we write like this.

for ( int i = 0 ; i != 101 ; ++i )
    ... ;

This works. But frankly, I don't want to write this code. I have to manually initialize a variable, check for loop terminating condition, and increment the variable, all by my hands. What I want is to iterate over integer of range a to b. You see, I can achieve this by just specify a and b. You can achieve that with iota(a, b+1).

for ( auto i : iota( 1, 101 ) )
    std::cout << i ;

Speaking of index loop, have you ever heard of the FizzBuzz problem? It goes like this "Print natural numbers 1 to 100. But for numbers multiple of 3, print Fizz instead of that number. For multiples of 5, print Buzz instead. For both multiple of 3 and 5, print FizzBuzz."

We have already written the index loop of 1 to 100. Let's write a function fizzbuzz(n) which take number n and return a string it should print to.

auto fizzbuzz = []( auto n ) -> std::string
{
    if ( n % 15 == 0 )
        return "FizzBuzz" ;
    else if ( n % 3 == 0 )
        return "Fizz" ;
    else if ( n % 5 = 0 )
        return "Buzz" ;
    else
        return std::to_string(n) ;
}

Now we wrote function fizzbuzz, we can use transform_view to transform the each elements in the range to corresponding string it should print to.

for ( auto msg : iota( 1, 101 )
    | transform( fizzbuzz )
)
    std::cout << msg << '\n' ; 

Isn't this fantastic?

Finally, you can combine as many views as you like. You can iota it, filter it, transform it, take it, then reverse it.

for ( auto i : iota(1)
    | filter(filter_rule)
    | transform(transfrom_rule)
    | take(n)
    | reverse
)
    std::cout << i '\n' ;

You can add even more views after reverse if you really want.

All the standard library's view can be use either as piping the function object

for ( auto n : iota(1, 100) | fileter(rule) | reverse )
    std::cout << n ;

or using as _view class.

iota_view i( 1, 100 ) ;
filter_view f( i, rule ) ;
reverse_view r( f ) ;

for ( auto n : r )
    std::cout << n ;

Both code do the same things. Basically, "A | B(args)" means a view object of "B_view( A, args )".

There are other views such as split_view which split the range to range of range separated by a specific value.

auto str = "quick brown fox" ;
std::vector< std::string > words ;
for ( auto word : str | split(' ') )
    words.emplace_back( begin(word), end(word) ) ;

after execution, words is {"quick", "brown", "fox"}

or join_view which flatten range of range to range.

std::string flat ;
for ( auto c : words | join )
    flat.push_back(c) ;

flat is "quickbrownfox".

All the example code assumes we use following using declarations.

using namespace std::ranges ;
using namespace std::ranges::view ;

So how do we write a view? Well, that's rather difficult if you want to write a standard library quality view. But let's try.

Let's write a drop_view which dropss n elements from the range.

for ( auto i : iota(0, 10) | drop(5) )
    std::cout << i ;

This code prints "56789".

Here is the implementation.

template < InputRange V >
    requres View<V>
class drop_view : public view_interface< dropVieww<V> >
{
    V base_ = V() ;
    iterator_t<V> iter = begin(base_) ;
    iter_difference_t<iterator_t<V>> count_ ;
public :
    drop_view() = default ;
    constexpr drop_view( V base, iter_difference_t<iterator_t<V>> count )
        : base_(base), iter( std::next( begin(base_), count ), count_(count)
    { }

    template < ViewableRange R >
        requires Constructible< R, all_view<R> >
    constexpr drop_view( R && base, iter_difference_t<iterator_t<V>> count )
        : base_(std::forward<R>(base)), iter( std::next( begin(base_), count ), count_(count)
    { }

    constexpr V base() const
    { return base_ ; }

    constexpr auto begin()
    { return iter ; }
    constexpr auto begin() const
    { return iter ; }

    constexpr auto end()
    { return end(base_) ; }
    constexpor auto end() const
    { return end(base_) ; }

    constexpr auto size()
        requires SizedRange<V>
    { return size(base_) - count_ ; }
    
    constexpr auto size() const
        requires SizedRange<const V>
    { return size(base_) - count_ ; }

    template < Range R >
    drop_view( R &&, iter_difference_t<iterator_t<V>> )
        -> drop_view< all_view<R> > ;
} ;


// I'm not 100% sure this is the correct way to implement range adaptor object.
// If my interpretation of the draft is correct, it should be.

struct drop_range_adaptor_closure_object
{
    std::size_t count ;
    drop_range_adaptor_closure_object( std::size_t count )
        : count(count)
    { }
    template < ViewableRange R >
    constexpr auto operator( R && r )
    {
        return drop_view( std::forward<R>(r), count ) ;
    }
} ;
struct drop_range_adaptor_object
{
    template < ViewableRange R >
    constexpr auto operator () ( R && r, iter_difference_t<iterator_t<R>> count )
    {
        return drop_view( std::forward<R>(r), count ) ;
    }

    constexpr auto operator () ( std::size_t count )
    {
        return drop_range_adaptor_closure_object( count ) ;
    }

} drop ;

template < ViewableRange R >
constexpr auto operator | ( R && r, drop_range_adaptor_closure_object a )
{
    return a(std::forward<R>(r)) ;
}

Phew, that's Eric Niebler-level of boilarplate-ness. I think we need to wait the metaclass to eliminate the boilarplate code. Hopefully, we can have a metaclass within another decade.

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はコンセプトとレンジのおかげで圧倒的に使いやすくなる。