2010-09-20

コンセプトの経緯

もう、この事に関しては、さんざんに書いたと思うのだが、もう一度書いておく。こんどは、具体的な例を挙げてみよう。

Rangeというコンセプトを考えるとする。ここでは、Rangeというコンセプトを満たす型は、Iterator T::begin(void)、Iterator T::end()というメンバー関数を持ち、それぞれ、先頭、終端のイテレーターを返すものとする、と定義する。もちろん、ネストされた型名も必要だ。

concept Range < typename T >
{
    typename Iterator ;
    Iterator T::begin() ;
    Iterator T::end() ;
}

ところで、もし誰かが、たまたま、全く同じシグネチャのメンバー関数とネストされた型名を持ったクラスを書いたとする。このクラスにおけるbegin()/end()は、イテレーターとは全く関係がない。ただ、シグネチャが偶然一致しただけである。

struct Foo
{
    typedef int Iterator ;
    Iterator begin() { return 0 ; }
    Iterator end() { return 0 ; }
} ;

もし、コンセプトマップが、すべてのクラスに対して自動的に生成されるとするならば、このような、偶然のシグネチャの一致に対しても、自動的にコンセプトマップが生成されてしまう。

その結果、本来ならば、「この型はRangeコンセプトを満たしていないよ」という、分かりやすいコンパイルエラーを出せるはずのコードに、「コンセプトはすべて満たしているけどエラーになった」などという、不思議なエラーメッセージが生成されてしまう。

これを防ぐには、コンセプトマップは自動的に生成されないようにして、もし、そのクラスがコンセプトの要求を本当に満たしているならば、そのクラスに対して、明示的にコンセプトマップを書く必要がある。

しかし、その場合、一見すると無駄な記述をしなければならない。

template < typename T>
concept_map Range< std::vector<T> > { }
template < typename T>
concept_map Range< std::deque<T> > { }
template < typename T>
concept_map Range< std::list<T> > { }

このように、明示的にコンセプトマップを生成しなければならない。中身は空である。というのも、STLのコンテナーの実装は、Rangeを満たしていて、何もマップする必要がないのだから。

コンセプトマップを自動的に生成されないようにすると、あらゆる型に対して、明示的にコンセプトマップを書かなければならない。自動的に生成されるようにすると、たまたまシグネチャが一致した場合に困る。

去年、この問題で大いに揉めた。ある者は、「たとえ空でも、絶対に明示的に宣言させるべきだ」と主張し、またある者は、「理想は結構だが、実質的に空の定義なんて、文法上のゴミと同じじゃないか」と主張した。議論の結果、自動的にコンセプトマップを生成するか、あるいは明示的に生成しなければならないかを、選べるようにしようという提案がなされた。

2009年のFrankfurt会議では、この提案が可決される予定であった。しかし、会議でも大いに揉めたらしい。いつまでたっても意見が一致しなかったので、投票がなされた。ドラフトからコンセプトを削除するという投票であった。あのBjarne Stroustrupも、あのDouglas Gregorも、削除に投票した。結果、コンセプトは削除されることになった。

No comments: