2014-11-04

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

N4165: Unified Call Syntax

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

この二つのC++標準化委員会の文章は、統一関数呼び出し文法を提案している。

現在、フリー関数とメンバー関数では、呼び出しの文法が異なる。フリー関数は、f( x, y, z )と呼び出すが、メンバー関数は、x.f( y, z )と呼び出す。これは汎用的なコードを書くのに都合が悪い。

template < typename T >
void f( T x )
{
    // Tがクラス型である場合これを使いたい
    x.swap( 1, 2 ) ;

    // Tがクラス型ではない場合これを使いたい
    f( x, 1, 2 ) ;
}

現状では、このようなコードは、テンプレートメタプログラミングの技法を駆使して、コンパイル時条件分岐を行わなければならない。この問題は、単に関数を呼び出す文法が統一されていないがために起こるのである。関数を呼び出す文法を統一してしまえばいい。

つまり、x.f(y)が、f(x,y)と同じ意味になるようにしてしまえばいいのだ。x.f(y)は、まずxのメンバーfを探す。見つからない場合は、f(x,y)と置き換えてfを探す。

struct X
{
    void f( int ) ;
} ;

struct Y { } ;

void f( Y &, int ) ;

int main()
{
    X x ;
    x.f( 0 ) ; // call X::f( 0 ) with x as this pointer

    Y y ;
    y.f( 0 ) ; // call f( y, 0 ) 
}

これが、N4165の提案だ。

N4174は、さらに、f(x,y)をx.f(y)と同じ意味にする提案もしている。すなわち、f(x,y)と書けば、フリー関数fがない場合、x.f(y)として呼び出してくれる。ただし論文では、フリー関数fが存在したとしても、メンバー関数を優先すべきであるとしている。

struct X { } ;

void f( X &, int ) ;

int main()
{
    X x ;
    x.f(0) ; // call f( x, 0 ) 
}

論文ではさらに、ツールサポートの充実を主張している。

テキストエディターでコードを記述する際に、既存の名前の一覧から、自動的に部分一致する名前を探しだして補完してくれる機能は、すでに一般的だ。

問題は、フリー関数の場合、その文脈で入力できる名前の候補があまりにも多いため、自動補完があっても面倒である。

そこでこの提案だ。たとえば、CライブラリのFILE構造体への操作だが、この提案が採用された場合、

FILE * fp = fopen(...) ;
fp->fseek

このように、FILE *を第一引数に取る関数だけに候補を絞り込める。

さらに提案では、fputsのような第一引数ではない関数にも対応するため、引数の場所をコンパイラーがよきにはからって決定してくれる案に言及している。

なるほど、論文の主張するツールサポートは頼もしそうに見えるが、問題は、stdio.hはC++ではdeprecated扱いで、cstdioを使うべきである。その場合、FILE構造体はstd名前空間スコープの中に入る。

すると何が起こるかというと、std名前空間が、ADLの連想名前空間に入る。std名前空間には、move, swap, addressofを始めとした、大量の汎用的すぎる関数テンプレートがある。

std::FILE * fp ;
fp->move

それだけではない。FILE構造体は通常ポインターとして使う。ポインターであるということは、たとえば<algorithm>をincludeしていた場合、イテレーターを第一引数に取る大抵の関数テンプレートが候補に上がってしまう。

fp->for_each

実際には使うことを想定していないこれらの名前で候補が膨れ上がってしまう。しかもこれは、既存のクラスの候補に及ぶ。標準ライブラリのクラスのメンバーの補完候補が大量の利用を想定していないがたまたまシグネチャが一致する関数名で膨れ上がってしまうし、intやdoubleやポインターといった基本的な型や、そういった型へのユーザー定義変換関数を定義しているクラスには、大量の候補で膨れ上がることになる。

どうもこの論文の主張するツールサポートの向上には賛同しがたいものがある。自動補完の候補は確かに減らせるが、それほど減らせるわけではなく、むしろゴミが増えてしまう。

色々と問題があるものの、関数呼び出しの文法の違いで汎用的なコードが書けないという問題は面倒なので、解決されて欲しい。

ドワンゴ広告

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

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

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

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

3 comments:

Anonymous said...

初段はいい提案に思います。
関数をあとから追記できるので見た目の上では素晴らしいと思います。
ツールサポートは発想は買いますがご指摘の通り見通しが若干甘いかなと思います。
インテリセンスではあんまり困ったことが無いのでこれ以上の改善は想像できないです。
まぁ完全に一意にできるんだったら話は別ですけども。

Anonymous said...

fがメンバーかどうかでx.f(y, ...)とf(x, y, ...)を呼び分けてくれる、std::call(f, x, y, ...)みたいなのがあるだけでよい感が……。

もしくは、C#のようにthisキーワードをつけるとメンバー風に呼び出せる、とか。

Anonymous said...

関数チェインできますね。
泥臭くて大好きなんですが・・・。:)