A note on namespace __cpo
namespace __cpo
Eventually 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::cbegin
doesn’t technically require the inline namespace because no type in thestd::ranges
namespace provides a hidden-friendcbegin
function. But that’s no guarantee it won’t happen in the future! (There is precedent for types with ADLbegin
functions;std::valarray
andstd::filesystem::directory_iterator
are two examples.) Likewise,std::views::take
doesn’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-friendtake
at 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::views
namespace!” Fair enough. But there might be in the future. Who knows? And what about names likecopy
andequal
, which are directly in thestd::ranges
namespace?
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
.