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