もう、この事に関しては、さんざんに書いたと思うのだが、もう一度書いておく。こんどは、具体的な例を挙げてみよう。
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:
Post a Comment