range-based forとstd名前空間
Boost.Coroutineに関してBoost users MLでrange-based forに関する面白い話が流れたので紹介したい。
この話は私も初めて聞いて驚いた。
C++11で新規追加されたrange-based forはconceptが削除されたタイミングでその挙動をどうするかで議論していた。なつかしい。
本の虫: range-based forに対する意見求む
- 配列を特別扱い
- class typeの場合、begin()/end()を呼べれば呼ぶ
- そうでなければstd名前空間をassociated namespacesに加えた上でADL
となりました。
しかし、ADLは挙動が非常にわかりづらく、特に問題なのがunqualified lookupするのかしないのかはADLの定義には書かれていません。
どちらかと言えば、unqualified lookupした結果、更にADLを加えるというのが本来意図された挙動でした。
さて、range-based forは最終的にADLするということになったわけですが、ここでunqualified lookupするかしないかという問題が出てきました。
結局この問題はDR1442*2となり、ADL時にはunqualified lookupをしないとなりました。
実際、GCCやClangといったコンパイラは、range-based for実装当初からunqualified lookupを行いません。
struct X { }; int main() { struct X x; int * begin(X &); int * end(X &); begin(x); // unqualified lookup for (auto _ : x) ; // エラー: begin/endが見つからない }
ところでこのDR1442ですが、よく見るとADLの挙動の明確化(unqualified lookupしないことの明確化)だけかと思うと、そうではありませんでした。
なんと、std名前空間をassociated namespaceに加えるという文言を削除しています。
つまり、これまでwell-formed*3だった以下のコードがill-formedとなるわけです。
namespace mylib { struct my_very_very_awesome_container { }; } namespace std { int * begin(mylib::my_very_very_awesome_container &) { return nullptr; } int * end(mylib::my_very_very_awesome_container &) { return nullptr; } } int main() { mylib::my_very_very_awesome_container c; for (auto x : c) (void)x; // std名前空間はassociated namespaceなのでbegin/endが呼ばれるはず }
なにせstd名前空間をlookup対象にするという文言を消しているので。
このassociated namespaceに関する挙動については、melpon氏謹製のwandboxによると
- clang 3.0 ~ 3.1
- gcc 4.6 ~ 4.8
の間は、よく知られた(と思う)std名前空間をlookupに含める挙動を示すようだ。
C++11として発行された規格ではstd名前空間を含めるはずだが、C++14ではなく、DRとして取り込まれたのでこれはもはやC++11の挙動だといって過言ではないだろう*4。
この挙動で頭を悩ませるのはboostのようなライブラリを書いている場合だろうから多分一般的には問題とはならないだろう。
Boost.Coroutineはstd名前空間にbegin/endを置いたので今回その対象となってしまった。
元議論:
Boost - Users - Cannot use Boost.Croutines with range based for
DR1442:
C++ Standard Core Language Defect Reports and Accepted Issues
*1:私はWGの人間ではないので積極的に議論されたかは知らない
*2:http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442
*3:これがwell-definedだと言い切る自信がない。言い切る自信がある方、ぜひコメント頂きたい
*4:保証はしない