本の虫

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

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

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

2014-10-pre-Urbana mailingsのレビュー: N4170-N4182

N4170: Extend INVOKE to support types convertible to target class (Revision 1)

タイトルだけですべてを説明している感があるが、INVOKEの仕様を拡張して、メンバーにアクセスする場合で、クラスに変換可能な場合に対応する。

読む限り、こういうコードが動くようになるのではないかと思うのだが、すでにGCCやClangで動くのでよく分からない。なにか読み間違えているのだろうか。

struct X
{
    int data = 0 ;
} ;

int main()
{
    std::function< int & ( X & ) > f = &X::data ;
    X x ;
    f( std::ref(x) ) = 0 ;

    auto data = std::bind( &X::data, std::ref(x) ) ;
    data() = 0 ;
}

N4171: Parameter group placeholders for bind

std::bindのplaceholderを拡張する提案。

std::bindには、placeholderという便利な機能がある。_1は第一引数、_2は第二引数というように、実引数をその順番で指定できるのだ。これにより、実引数の順番を入れ替えたり、無視したりといったことができる。

struct Func
{
    void operator () () const
    { std::cout << '\n' ; }

    template < typename T, typename ... Types >
    void operator () ( T && head, Types ... tail ) const
    {
        std::cout << head ;
        (*this)( tail... ) ;
    }

} ;

int main()
{
    using namespace std::placeholders ;

    auto f1 = std::bind( Func(), _1, _2, _3 ) ;
    f1( 1, 2, 3 ) ; // 123

    auto f2 = std::bind( Func(), _3, _2, _1 ) ;
    f2( 1, 2, 3 ) ; // 321

    auto f3 = std::bind( Func(), 4, _3, 4 ) ;
    f3( 1, 2, 3 ) ; // 434
}

N4171は、このプレイスホルダーを拡張する。すべての実引数を表す_all。 N番目から最後までの実引数を表す_from<N>。最初からN番目までの実引数を表す_to<N>。N番目からK番目までの実引数を表す_between<N, K>。

struct Func
{
    void operator () () const
    { std::cout << '\n' ; }

    template < typename T, typename ... Types >
    void operator () ( T && head, Types ... tail ) const
    {
        std::cout << head ;
        (*this)( tail... ) ;
    }

} ;


int main()
{
    using namespace std::placeholders ;

    auto f1 = std::bind( Func(), _all ) ;
    f1( 1, 2, 3 ) ; // 123
    f1( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 123456789

    auto f2 = std::bind( Func(), _from<5> ) ;
    f2( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 56789

    auto f3 = std::bind( Func(), _to<5> ) ;
    f3( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 12345

    auto f4 = std::bind( Func(), _between<3,7> ) ;
    f4( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; // 34567

    auto f5 = std::bind( Func(), _all, _all ) ;
    f5( 1, 2, 3 ) ; // 123123

    auto f6 = std::bind( Func(), _to<3>, _from<6,9>, _between<4,5> ) ;
    f6( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) ; //123678945 
}

また論文は、ジェネリックlambda式や汎用ラムダキャプチャーがある現在、std::bindに存在価値はあるのかという議論に対して反論を試みている。std::bindを使ったほうが簡潔で便利な場合があるという。 Perfect Forwardingを自力で書くのは面倒なので、std::bindの簡潔さは魅力だ。

面白いし、簡潔に書ける場合もあるだろうが、私はlambda式の方が好みだ。

N4172: A proposal for named arguments for C++

名前付き実引数の提案。

name : valueという文法で、仮引数の名前を指定して実引数を渡すことができる。


// グラフィック用のライブラリなどでありがちな引数の多い関数
void draw_rect(int left, int top, int width, int height);

int main()
{
    // どれがどれだっけ?
    draw_rect( 100, 100, 400, 400 ) ;

    // わかりやすい
    draw_rect( left: 100, top : 100, width : 400, height : 400 ) ;

    // 順番を入れ替えることもできる
    draw_rect( width : 400, height : 400, top : 0, left : 0 ) ;
}

これを名前付き実引数(named arguments)と呼ぶ。従来の実引数を、positional argumentsと呼ぶ。positional argumentsは、名前付き実引数の後に書くことはできない。

void f( int a, int b ) ;

int main()
{
    f( 1, b : 2 ) ; // well-formed
    f( a : 1, 2 ) ; // ill-formed 
}

ellipsisやVariadic Templatesを仮引数に使った関数の場合は、名前付き実引数は使えない。


template < typename ... Types >
void f( int a, Types ... args ) ;

void g( int a, ... ) ;

int main()
{
    f( a : 0 ) ; // ill-formed
    g( a : 0 ) ; // ill-formed
}

コンストラクター呼び出しにリスト初期化を使う場合には、名前付き実引数が使える。

struct X
{
    X( int a, int b, int c )
} ;

X x1( a : 1, b : 2, c : 3 ) ;
X x2{ a : 1, b : 2, c : 3 } ;

ただし、提案はアグリゲート初期化には禁止している。


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

X x{ a : 1, b : 2, c : 3 } ; // ill-formed

これを禁止する理由としては、後からコンストラクターを追加した場合、仮引数の名前とデータメンバーの名前とで、プログラムの意味が変わってしまうからだとしている。

関数ポインターを経由した関数呼び出しには、名前付き実引数は禁止されている。

void f( int x, int y ) ;
using pointer = void (*)( int a, int b ) ;

pointer p = &f ;

この例で、関数ポインターpを経由して関数呼び出しをする際、xとyはもちろん使えない。関数ポインターが指し示す先は実行時に決まるためだ。aとbも使えない。理由は、aとbを使えるようにするためには、typedef名に仮引数名まで型情報として含めなければならないからだ。

この提案は、関数の型に仮引数名を含める実装方法を取らないことを前提に設計されている。

また論文では、予想される反論に回答を与えている。

そもそも引数の数が多すぎる関数は設計が間違っているという反論に対しては、「それはそうだが、既存の現実のAPIは引数の数が多く、名前付き実引数は現実の問題を解決できる」と。

C99のdesignated initialierと衝突するという反論に対しては、「あちらはデータメンバー名であり衝突しない」と

仮引数の名前が変わると呼び出し側の意味が変わってしまうという反論に対しては、既存の有名なライブラリの最新版と数年前のバージョンのdiffを調査したところ、仮引数名の変更は極めて少なかったという調査結果を示している。

また、将来の拡張案として、デフォルト実引数や、名前付きテンプレート実引数を挙げている。

以下のようなデフォルト実引数が動くようになる。

void f( int a = 0, int b ) ;

int main()
{
    f( b : 0 ) ; // OK
}

論文では、この挙動に異論はないが、この提案では含めないとしている。

名前付きテンプレート実引数とは、以下のようなコードのことだ。

template < typename Key, typename Value >
struct map { ... } ;

map< Key : int, Value : string > m ;

論文は、これは別物であり、別の提案で取り上げられるべきだとしている。

N4173: Operator Dot

operator .をオーバーロード可能にする提案。

class string_ref
{
    std::string data ;

public :
    string_ref( std::string const & value )
        : data( value ) { }

    std::string & operator .() { return data ; }
} ;

int main()
{
    string_ref ref("hello") ;

    ref.length() ; // ref.operator.().length()
    std::string str("str") ;
    ref = str ; // ref.operator.() = str
}

operator *やoperator ->をオーバーロードしてポインターのように振る舞うクラスを作ることができるように、operator .をオーバーロードすれば、リファレンスのように振る舞うクラスを作ることができる。代入演算子すら使える。

メンバーを明示的に宣言することによって、上書きできる。

// resizeのあとにshrink_to_fitするstring
class shrink_string
{
    std::string data ;
public :
    std::string & operator .() { return data ; }
    void resize( std::size_t n )
    {
        data.resize( n ) ;
        data.shrink_to_fit() ;
    }
} ;

用途としては、スマートポインターのようなスマートリファレンスを書くのに使える。

N4174: Call syntax: x.f(y) vs. f(x,y)

本の虫: C++に提案されている統一関数呼び出し文法(Unified Call Syntax): N4165, N4174を参照。

N4175: Default comparisons

デフォルトの比較演算子を暗黙的に生成する提案。

前回の提案では、明示的に生成するよう記述した場合のみ生成するようになっていた。これは、既存のコードの意味を変えないという点では素晴らしいが、煩わしい。(a!=b) != !(a==b)がtrueとなるような珍妙なクラスを書いた奴は、根本的な論理的問題を引き起こしているのであって、そんなマヌケを救う必要はない。

とはいえ、色々と考えることがある。

==だけがユーザー定義されていた場合は、!=は==を使って生成される。では逆に!=だけがユーザー定義されていた場合はどうか。論文著者のBjarne Stroustrupは、!=から==を生成するのは好ましくないので生成しないと書いている。

デフォルト生成された比較演算子にエラーがあって生成できない場合は、演算子を使った場合にエラーとなる。これは代入演算子の場合と同じだ。

==の定義は、クラスの各メンバーをそれぞれ比較して、どれかひとつでもfalseを返した場合は、結果がfalseとなる。

!=の定義は、a!=bは!(a==b)と定義される。

クラスがポインター型のメンバーを持っている場合は、比較演算子は生成されない。

リファレンス型のメンバーは、普通に比較される。

配列型のメンバーは、各要素がそれぞれ比較される。

mutableメンバーの扱いは難しい。人によって、mutableはクラスの値の一部であるとも、そうでないとも、意見が分かれる。論文では、

  1. mutableメンバーを他のメンバーと同様に扱う
  2. mutableメンバーがあるクラスに対しては比較演算子を生成しない
  3. mutableメンバーは比較の際には無視する

という三つの方法を上げたうえで、3.の無視を提案している。

空のクラスの場合は、等しいと評価される。

unionの場合は、デフォルトの比較演算子は定義されない。ユーザー定義の==がある場合は!=は生成される。

派生がある場合、仮想関数がない場合に、基本クラスを隠しメンバーとみなして比較する。

N4176: Thoughts about Comparisons

N4175に対する議論をまとめたもの。

N4177: Multidimensional bounds, index and array_view, revision 4

連続したストレージを多次元配列のように扱えるラッパークラスライブラリ、array_viewの提案。

前回からの変更点は、array_viewとstrided_array_viewのコンストラクターの引数の順序を、既存のSTLの慣習に合わせて、{size, location}から{location, size}に変更したこと。定数のviewに対するエイリアステンプレートを追加したこと。

N4178: Proposed resolution for Core Issue 330: Qualification conversions and pointers to arrays of pointers

§4.4の文面では、多次元ポインターについて規定しているが、ポインターの配列へのポインターの場合に対する記述がなく、以下のコードが型変換できないという問題がある。

int main()
{
    double *array2D[2][3];

    double       *       (*array2DPtr1)[3] = array2D;     // Legal
    double       * const (*array2DPtr2)[3] = array2DPtr1; // Legal
    double const * const (*array2DPtr3)[3] = array2DPtr2; // Illegal
}

以下のコードは合法であることを考えると、これはおかしい。

int main()
{
    double *array[2];

    double       *       *ppd1 = array; // legal
    double       * const *ppd2 = ppd1;  // legal
    double const * const *ppd3 = ppd2;  // certainly legal (4.4/4)
}

したがって、現行の文面に従えば、reinterpret_castを使うしか方法がなくなる。

この問題を解決する。

きわめてさらっと触れられているが、この修正案は、reinterpret_castにも手を加えている。なんと、reinterpret_castでconst性を削ぎ落とすことが可能になった。

void f( void const * ptr )
{
    // 現行ではill-formed
    // N4178提案ではwell-formed
    reinterpret_cast< int * >( ptr ) ;
}

いまさらこの制約をあっさりと外すのだろうか。reinterpret_castを使う以上、別にconst性をそぎ落としても問題ないのではないかとは思っていたものの、このような大きな変更にしては意外の軽さだ。

N4179: Transactional Memory Support for C++: Wording (revision 2)

トランザクショナルメモリーの文面案

N4180: SG5 Transactional Memory Support for C++ Update

トランザクショナルメモリーに対するN3999からの変更点。

N4182: SG5: Transactional Memory (TM) Meeting Minutes 2014/07/14-2014/10/06

トランザクショナルメモリーの会議の議事録。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

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

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

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