2

A note on `namespace __cpo` – Arthur O'Dwyer – Stuff mostly about C++

 2 years ago
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.
neoserver,ios ssh client

A note on 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 the std::ranges namespace provides a hidden-friend cbegin function. But that’s no guarantee it won’t happen in the future! (There is precedent for types with ADL begin functions; std::valarray and std::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 named take, which raises the terrifying possibility of some standard type acquiring a hidden-friend take at some point in the future. If this ever happened, we might have to go add an inline namespace around std::ranges::cbegin, or around std::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 like copy and equal, which are directly in the std::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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK