C++20標準ライブラリで導入された Customization Point Object (CPO)定義で必要となる Poison-pill*1 オーバーロードについてメモ。std::ranges::swapやstd::ranges::begin/endなどのCPO定義で利用される。
本記事の内容はStackOverflowで見つけた質問と回答に基づく。
std::ranges名前空間でのCPO定義位置からは、親名前空間stdで定義されるカスタマイズポイント同名の制約のない関数テンプレート(std::swapやstd::begin/end)が "見えて” しまうため、同関数テンプレートシグネチャをdelete宣言してオーバーロード候補から除外する(★印)。
- 2023-02-27追記:C++2b(C++23)向け提案文書P2602R2が採択され、(下記例示
swap*2とiter_swapを除く)CPO実装において Poison-pill オーバーロード が調整される。詳細は [C++]WG21月次提案文書を眺める(2022年06月) - 地面を見下ろす少年の足蹴にされる私 を参照。
// std::ranges::swap CPO実装イメージ(超簡略化版) namespace std::ranges { namespace swap_impl { template<class T> void swap(T&, T&) = delete; // ★ struct swap_fn { template<class T1, class T2> requires /* C++20 18.4.9/p2/b1 */ constexpr void operator()(T1& e1, T2& e2) const { // 非修飾名・ADL経由でカスタマイズポイント(swap)を呼び出す swap(e1, e2); } // ... }; } // std::ranges::swap CPO定義 inline namespace swap_cpo { inline constexpr swap_impl::swap_fn swap{}; // Hidden friendとの名前衝突回避のためインライン名前空間が必要 // 詳細説明は提案文書 P1895R0 を参照のこと } }
- GCC/libstdc++: https://github.com/gcc-mirror/gcc/blob/releases/gcc-10.2.0/libstdc++-v3/include/std/concepts#L169
- MSVC: https://github.com/microsoft/STL/blob/58160d548f3583b3232129ea38d786ad583ca65c/stl/inc/concepts#L135-L136
C++20 Rangesライブラリの前身、Ranges TS検討当時の提案文書 P0370R3 Ranges TS Design Updates Omnibus より一部引用。
unqualified name lookup for the name
swapcould find the unconstrainedswapin namespacestdeither directly - it’s only a couple of hops up the namespace hierarchy - or via ADL ifstdis an associated namespace ofTorU. Ifstd::swapis unconstrained, the concept is "satisfied" for all types, and effectively useless. The Ranges TS deals with this problem by requiring changes tostd::swap, a practice which has historically been forbidden for TSs. Applying similar constraints to all of the customization points defined in the TS by modifying the definitions in namespacestdis an unsatisfactory solution, if not an altogether untenable.
We propose a combination of the approach used in N4381 with a "poison pill" technique to correct the lookup problem. Namely, we specify that unqualified lookup intended to find user-defined overloads via ADL must be performed in a context that includes a deleted overload matching the signature of the implementation in namespace
std. E.g., for the customization pointbegin, the unqualified lookup forbegin(E)(for some arbitrary expressionE) is performed in a context that includes the declarationvoid begin(const auto&) = delete;. This "poison pill" has two distinct effects on overload resolution. First, the poison pill hides the declaration in namespacestdfrom normal unqualified lookup, simply by having the same name. Second, for actual argument expressions for which the overload in namespacestdis viable and found by ADL, the poison pill will also be viable causing overload resolution to fail due to ambiguity. The net effect is to preclude the overload in namespacestdfrom being chosen by overload resolution, or indeed any overload found by ADL that is not more specialized or more constrained than the poison pill.
C++20(N4861) 16.4.2.2.6/p6より引用(下線部は強調)。
[Note: Many of the customization point objects in the library evaluate function call expressions with an unqualified name which results in a call to a program-defined function found by argument dependent name lookup (6.5.2). To preclude such an expression resulting in a call to unconstrained functions with the same name in namespace
std, customization point objects specify that lookup for these expressions is performed in a context that includes deleted overloads matching the signatures of overloads defined in namespacestd. When the deleted overloads are viable, program-defined overloads need be more specialized (13.7.6.2) or more constrained (13.5.4) to be used by a customization point object. -- end note]
関連URL