本の虫

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

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

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

C++標準化委員会の文書: P0501R0-P0549R0

タイトルの範囲で興味深い文書だけ紹介。

[PDF] P0506R0: use string_view for library function parameters instead of const string & / const char *

string_viewで既存の標準ライブラリのstringやchar const *を引数に取る部分を全部置き換える提案。これにより規格の文面も標準ライブラリの定義もかなり短縮される。

ABI互換をぶち壊しそうだがそれに対する考察がない。

[PDF] P0515R0: Consistent comparison

operator <=>の提案。

これまで、様々な種類の比較(strong/weak/partial orderingやequality)について、それぞれstrong_order_less_thanとかweak_order_less_than

operator <=>はthree-way comparisonを提供する。

a <=> bを評価した結果の値rは、a < bの場合 r < 0。a > bの場合r > 0。a == bの場合r == 0となる。

operator <=>によって、比較の種類の問題が解決できる。戻り値の型によって種類を表せばよい。例えばある型がstrong orderingをサポートしている場合は、以下のように書く。

class X
{
    int a ;

public :
    friend std::strong_ordering operator <=>( X const &, X const & ) = default ;
} ;

weak_orderingしか対応できない型の場合は、以下のように書く。例えば、大文字小文字を区別しない文字列型は、weak orderingしか提供できない。


class CaseInsensitiveString
{
    std::string s ;
public :
    frined std::weak_ordering operator <=>( CaseInsensitiveString const & a, CaseInsensitiveString const & b )
    {
        // 大文字小文字を区別しない比較を行う関数
        return case_incensitive_compare( a, b ) ;
    }
} ;

比較の種類を型システムに載せることで、より強い比較を無理やり提供しようとするとコンパイルエラーになる。


class X
{
    CaseInsensitiveString s ;
public :
    // コンパイルエラー
    friend std::strong_ordering operator <=> ( X const &, X const & ) = default ;
} ;

同様に、partial orderingにしか対応できない型は、std::partial_orderingを返す。また、大小比較を提供できず、等価比較しか出来ない場合は、std::strong_equalityやstd::weak_equalityを返す。

さて、残りの比較演算子は、すべてoperator <=>から生成できる。

operator <=>をユーザーが使うこともできるが、通常はその必要はない。というのも、a < bを実現するには、(a <=> b) <0と書かなければならないからだ。

これは今までの提案よりだいぶマシな提案だ。

[PDF] P0533R0: constexpr for <cmath> and <cstdlib>

<cmath>と<cstdlib>の関数の内constexpr実装できるものの基準の考察

[PDF] P0534R0: call/cc (call-with-current-continuation): A low-level API for stackful context switching

call/ccを実現するライブラリの提案。

P0535R0: Generalized Unpacking and Parameter Pack Slicing

これは興味深い提案。

パラメーターパックのスライシングができる機能の提案。

template < typename ... pack >
struct X
{
    // 1番目のパラメーター
    using t1 = [0]pack ;
    using t1a = []pack ; 

    // 2番目のパラメーター
    using t2 = [1]pack ;
    
    // 最後のパラメーター
    using t_last = [-1]pack ;
    // 最後の一つ前のパラメーター
    using t_before_last = [-2]pack ;



    // 3番目から6番目までの3つのパラメーターを持つ新たなパラメーターパック
    using t3 = std::tuple<[2:5]pack> ;
    // 2番目から最後までのパラメーターを持つ新たなパラメーターパック
    using t4 = std::tuple<[1:]pack> ;
    // 1番目から5番目までの4つのパラメーターを持つ新たなパラメーターパック
    using t5 = std::tuple<[:4]pack> ;
    // packと同じパラメーターパック
    using t4 = std::tuple<[:]pack>
} ;

また、以下のような使い方もできる。


struct X
{
    int a ;
    int b ;
    int c ;
} ;

X x ;
[0]x = 1 ; // x.a = 1
[1]x = 2 ; // x.b = 2
[2]x = 3 ; // c.c = 3

興味深いし便利だ。

P0536R0: Implicit Return Type and Allowing Anonymous Types as Return Values

匿名型を関数の戻り値の型に記述できる提案。

struct { int id ; double value } f() ;

この関数の宣言に対して、後から定義を書く際には、以下のように書ける。


decltype(return) f()
{
    return { 123, 5.0 } ;
}

decltype(return)はすでに宣言された関数の戻り値の型を示す。したがって、以下のような例はエラーとなる。


int f(int) ;
float f(float) ;

// エラー、すでに宣言された関数と一致しない
decltype(return) f( double d )
{
    return d ;
}

すでに、関数の戻り値の型推定があるので、以下のようには書ける。


auto f()
{
    struct { int id ; double value ; } result( 123, 5.0 ) ;
    return result ;
}

この提案は、関数のシグネチャをあらかじめ宣言しておけるという機能を提供する。

どうもこのまま受け入れるには問題の多い曖昧な提案だ。

P0538R0: A Qualified Replacement for #pragma once

#pragma onceの機能を標準に追加する提案。

#pragma onceとは主要なC++コンパイラーが実装している非標準機能で、#pragmra onceを書いたヘッダーファイルの#includeを一回のみにする機能だ。

ヘッダーファイルの多重includeを防ぐために、伝統的に以下のようなinclude guardと呼ばれる方法が用いられてきた。

// foo.h
#ifndef _MYLIB_FOO_H_INCLUDED
#define _MYLIB_FOO_H_INCLUDED
...
#endif // _MYLIB_FOO_H_INCLUDED

今回提案されている#onceディレクティブは、ユニークな識別子が必要となる。

#once identifier [ <whitespace> version ]

識別子は::で結合することができる。これは名前空間的に使うことができる。また、識別子の後に空白文字に続けてバージョン番号を記述できる。

すでに一度#includeしたヘッダーと同じ識別子を使っている#onceディレクティブは、残りのヘッダーが無効化される。


// foo.h

#once mylib::foo

提案では、#forgetというディレクティブも提案している。これは既存の識別子を忘れることで、#onceの書かれたヘッダーを多重includeできるようにする機能だ。必要性が理解できない。

結局、一言でまとめれば、提案されている機能は伝統的なinclude guardのシンタックスシュガーだ。

P0539R0: A Proposal to add wide_int Class

wide_int<bytes, signed>型の追加。

long long int型の追加で、64bit長の整数型は表現できるようになったが、それ以上のビット長の整数型を扱いたい場合に標準で表現することが出来ない。そこで、ライブラリで任意のバイト長の整数型を表現できるものを入れようと言う提案。

template<size_t Bytes, bool Signed> class wide_int;

P0540R0: A Proposal to Add split/join of string/string_view to the Standard Library

split/joinをstringとstring_viewとregexに提供する提案。

splitとjoinはメンバー関数という形で提供される。

splitには3種類ある。splitsとsplitfとsplitcだ。これらはセパレーターを引数に取る。文字列をセパレーターで分割する。

セパレーターは、文字、文字列、regexのいずれかだ。セパレーターの文字数がゼロもしくはnull文字ひとつの場合、文字型ひとつづつでsplitされる。

int main()
{
    using std::literals ;
    auto str = "aaa bbb\nccc ddd"s ;

    auto r1 = str.splits(' ') ;
    // r1は{"aaa", "bbb\nccc", "ddd"}

    auto r2 = str.splits("bbb") ;
    // r2は{"aaa", "\nccc ddd"}

    auto r3 = str.splits( std::regex(R"(\s)") ) ;
    // r3は{"aaa", "bbb", "ccc", "ddd"}

    auto str2 = "abc"s ;
    auto r4 = str2.splits("") ;
    // r4は{"a", "b", "c"}
}

splitsは結果をvector<string>で返す。コンテナーをハードコードしている理由は、簡単なライブラリにしたいためだ。splitvは結果をvector<string_view>で返す。こちらは文字列をコピーしないでstring_viewの参照で返す。

vector<basic_string<CharT, Traits> > splits(const basic_string_view<CharT, Traits> &Separator) const
vector<basic_string_view<CharT, Traits> > splitsv(const basic_string_view<CharT, Traits> &Separator) const 

splitfは分割した文字列を関数オブジェクトに渡す。

template <class F>
void splitf(const basic_string_view &Separator,F functor) const

以下のように使う。

int main()
{
    using std::literals ;
    auto str = "a b c" ;
    str.splitf( ' ', []( auto s ) { std::cout << s << '\n' ; } ) ;
}

splitcは分割した文字列をコンテナーにemplace_backしていく。これにより、vector以外のコンテナーを使いたい場合に使える。

int main()
{
    using std::literals ;
    auto str = "a b c" ;
    std::list<std::string> c ;
    str.splitc( ' ', c ) ;
    // cは{"a", "b", "c"}
}

このコンテナーへのリファレンスを取る既存の標準ライブラリに似つかわしくないデザインについて提案著者に質問したところ、お手軽に使いたいからとのこと。あまりよろしくない動機だ。

P0543R0: P0543R0: Saturation arithmetic

C++で符号なし整数は演算結果が最低値、最大値を上回る場合、アンダーフロー、オーバーフローするが、最低値、最大値になって欲しい場合がある。そのためのsaturation演算を提供する提案。

具体的にはコードを見ると一目瞭然。

int main()
{
    // 7
    auto r1 = satadd( 3, 4 ) ;
    // 3
    auto r2 = std::numeric_limits<unsigned int>::max() + 4 ;
    // std::numeric_limits<unsigned int>::max()
    auto r3 = satadd( std::numeric_limits<unsigned int>::max(), 4 ) ;
}

便利だ。

多くのアーキテクチャのSIMD命令は、saturation演算を提供している。

P0544R0: User Injection of Filesystems

std::filesystemにユーザー側が独自の実装を追加することができる昨日の提案。

ユーザースペースによるファイルシステムの実装、例えばアーカイブファイルをファイルシステムとして扱うとか、メモリ内キャッシュをファイルシステムとして扱うような場合に、std::filesystemの実装をユーザー側が提供したいことがある。そのためにstd::filesystemの実装を追加できるようにする機能が必要だ。

また、filesystemの意図的にエラーを発生させることによるエラー時の処理のテストにも使える。

P0545R0: Supporting offsetof for Stable-layout Classes

クラスのメンバーのクラスレイアウト上のオフセットを求めるoffsetofは、standard layout classにしか使えない。しかし、standard layout classは制限が厳しすぎる。例えば、派生やコンストラクターやコピー代入演算子などがあるだけでもstandard layout classから外れる。

しかし、クラスのレイアウトを決定するのに、コンストラクターやコピー代入演算子の存在の有無が影響を与える必要はない。実際、Itanium ABIやWindows x64 ABIはそのようになっている。offsetofを適用できるクラスを増やすために、クラスのレイアウトがコンパイル時に決定できるstable layout classを新たに定義しようという提案。

ある型がstable layout class型となるためには、

virtual関数についても、主要な実装方法では、隠しデータメンバーとして実際の関数へのオフセットを保持するvtableを持つ。このメンバーのサイズと位置は固定なのでレイアウトはコンパイル時に計算できる。実際、GCC, Clang, MSVCではそうなっている。しかし、今回の提案では、virtual関数を持つ型がstable layout classを満たすことについてはconditionally supportedに留める。将来的には緩和を考える。

virtual基本クラスはvirtual関数と同じように隠しデータメンバーとして持つ。virtual関数とは違いそのサイズと位置は可変だ。最終的な最も派生されたクラス型がわかっているならばコンパイル時にレイアウトの計算ができるが、一般的にはコンパイル時に計算できないので、サポートしない。

ドワンゴ広告

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

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

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