A note on namespace __cpo
namespace __cpoEventually I’ll write up a full “libc++ beginner’s guide to writing niebloids.” For now, this is just a quick note to explain one particular quirk. The shape of a libc++ niebloid and/or CPO is:
namespace std::ranges {
namespace __iter_swap {
struct __fn {
auto operator()(~~~) const { ~~~ }
};
}
inline namespace __cpo {
inline constexpr auto iter_swap = __iter_swap::__fn{};
}
}
Why do we have that extra inline namespace __cpo? Why not simply do this?
namespace std::ranges {
namespace __iter_swap {
~~~
}
inline constexpr auto iter_swap = __iter_swap::__fn{};
}
Well, you see, in another part of the forest we have counted_iterator,
which has an ADL iter_swap defined using the hidden friend idiom:
namespace std::ranges {
template<~~~>
struct counted_iterator {
friend void iter_swap(counted_iterator, counted_iterator) { ~~~ }
};
}
Hidden friends are hidden from qualified name lookup, but they still
possess qualified names, according to their associated namespace.
Here we’re telling the compiler that for each instantiation of counted_iterator
there exists a function std::ranges::iter_swap (which btw is hidden from ordinary
qualified lookup). There might be many such functions; that’s okay
because functions can be overloaded.
But the compiler will complain loudly if we tell it that std::ranges::iter_swap
is both a function and a variable! Variables can’t be “overloaded” with
functions in that way.
int f(int); int f(double); // OK
int f(int); int f; // Error
So any time any class std::ranges::Foo has a hidden friend named bar,
we can never define std::ranges::bar as a variable.
But we can define std::ranges::__cpo::bar as a variable! And we can mark
__cpo as an inline namespace, so that std::ranges::__cpo::bar will be
found by ordinary qualified lookup of std::ranges::bar. (Unambiguously,
because all the functions named std::ranges::bar that might compete with it
are defined as hidden friends.)
So, for certain identifiers such as iter_swap, swap, and iter_move,
some little quirk like namespace __cpo is simply mandatory. For other identifiers,
such as take and equal and (AFAIK) cbegin, the extra namespace
is not required today — but libc++ (as of this writing) does it anyway, for
two reasons:
-
Consistency is the best policy. It’s easier to just be consistent, than to have to worry about “Should I create the inline namespace for this particular niebloid or not? What are our criteria?” The criterion is simple: Just do it.
-
Future-proofing. Today,
std::ranges::cbegindoesn’t technically require the inline namespace because no type in thestd::rangesnamespace provides a hidden-friendcbeginfunction. But that’s no guarantee it won’t happen in the future! (There is precedent for types with ADLbeginfunctions;std::valarrayandstd::filesystem::directory_iteratorare two examples.) Likewise,std::views::takedoesn’t require the inline namespace today, but there is already a WG21 proposal (Giuseppe D’Angelo’s P2226 “An idiom to move from an object and reset it to its default-constructed state”, October 2020) to add an ADL customization point namedtake, which raises the terrifying possibility of some standard type acquiring a hidden-friendtakeat some point in the future. If this ever happened, we might have to go add an inline namespace aroundstd::ranges::cbegin, or aroundstd::views::take. Which would be an ABI break. So let’s just do it right the first time.
“But there are no types in the
std::viewsnamespace!” Fair enough. But there might be in the future. Who knows? And what about names likecopyandequal, which are directly in thestd::rangesnamespace?
Microsoft STL appears to do the same inline-namespace trick
for iter_swap
and all the other CPOs
(cbegin, data, strong_order,…) but stops there: it does not
extend the franchise to all niebloids (take, copy, equal,…)
as a general rule. Its inline namespace is namespace _Cpos.
Likewise, GNU libstdc++ appears to do the trick
for most CPOs,
but notably not for strong_order etc., and not for all niebloids (take, copy, equal…)
as a general rule. Its inline namespace is namespace __cust.
