A note on `namespace __cpo` – Arthur O'Dwyer – Stuff mostly about C++
source link: https://quuxplusone.github.io/blog/2021/12/07/namespace-cpo/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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
.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK