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
swap
could find the unconstrainedswap
in namespacestd
either directly - it’s only a couple of hops up the namespace hierarchy - or via ADL ifstd
is an associated namespace ofT
orU
. Ifstd::swap
is 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 namespacestd
is 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 namespacestd
from normal unqualified lookup, simply by having the same name. Second, for actual argument expressions for which the overload in namespacestd
is 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 namespacestd
from 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