The ADL two-step is the classic protocol: bring the std fallback into scope, call unqualified, let ADL find the user's overload.
template <class T>
void reset(T& a, T& b) {
using std::swap;
swap(a, b);
}
No change; std::begin/std::end free functions follow the same pattern for ranges.
std::invoke unifies “call anything callable”, which generic code adopts as its dispatch primitive. The two-step remains the customization protocol.
Ranges ships customization point objects: std::ranges::swap, ranges::begin are callable objects that do the two-step internally and enforce constraints. Hidden friends become the preferred hook to customize.
std::ranges::swap(a, b);
class Buf {
friend auto begin(Buf& b) { return b.data_; }
};
tag_invoke style (one ADL name, dispatch on a tag object) peaks in the senders/receivers proposals and libraries like libunifex — maximal protection from name collisions, at a real readability cost.
The committee pivots: standardized std::execution drops tag_invoke for plain member functions (connect, start, queries) — easier to teach, debug and tool. Lesson: member-first, CPO at the boundary, tag_invoke only with strong reasons.
struct my_sender {
template <class Rcvr>
auto connect(Rcvr r) { return my_opstate<Rcvr>{std::move(r)}; }
};
C++29direction · not adopted
Direction: a language-level customization mechanism (the P2279 problem statement) and reflection-assisted interface checking remain open Evolution work.