2

I would like to write a concept that would allow me to verify if a given statement, using ADL, is valid and has a proper return type.

In my specific case, I want to write a String concept that should enforce the following requirements:

  • The type has begin and end semantics for both its const and non-const flavors:

     // Akin to what this function does for `T` and `T const`
     template< typename T >
     auto test(T& t) {
         using std::begin;
         return begin(t);
     }
    
     // calling test with `T` and `T const` should be valid.
    
  • The return types of begin and end are consistent for both const and non-const flavors and they satisfy the std::contiguous_iterator concept.

So far I went with a trait implementation like the following:

namespace Adl {

using std::begin;

template< typename T >
using HasBeginT = decltype(begin(std::declval< T >()));

}

template< typename T >
using HasBegin = std::experimental::is_detected< Adl::HasBeginT, T >;

But I would like to directly embed this ADL usage in the definition of my constraint instead.

Rerito
  • 5,886
  • 21
  • 47

1 Answers1

2

The idiom using std::X; X(...); is considered a bad idea post-C++20. The standard idiom now is to create a customization point object for X. Or in your case, use the existing customization point object: std::ranges::begin.

The way you call such a customization point is by spelling it out in full; internally, it can make an ADL call (without using anything) if the type being provided has such a call.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • That's very interesting and new to me (hence why my approach seemed weird). With your suggestion, an implementation of my concept looks like this: http://coliru.stacked-crooked.com/a/431f8c7295825e1b. Do you have a suggestion for any improvements? – Rerito Feb 06 '21 at 20:20
  • How would you write a pseudo-`begin` customization point object so that it perform and ADL call with, say, a hypothetical `ns::begin`? Write that CPO inside the namespace `ns` and not use `using` at all? – Rerito Feb 06 '21 at 20:22
  • @Rerito: The purpose of `using std::begin` in the old idiom is to bring the name of `std::begin` into the current name lookup so that types that have no namespace can still find one (like the overload for C arrays). In the new idiom, you can write those defaults as *part* of the customization point. – Nicol Bolas Feb 06 '21 at 20:27
  • I understand, that's what I saw with the implementation of `std::ranges::begin` which features a case for C arrays. Thanks for the heads up – Rerito Feb 06 '21 at 20:53
  • 2
    @Rerito You should look at the concepts that the standard library already has, since you're reinventing them. Your `BeginType` is `std::ranges::iterator_t`, your `EndType` is `std::ranges::sentinel_t`, and having those types be the same is `std::ranges::common_range`. Checking contiguity is `contiguous_range`. – Barry Feb 06 '21 at 23:07
  • @Barry Thank you, since these are new to me I didn't know where to look. Thanks to your and Nicol Bolas' tips, I'm slowly browsing the STL docs you mentioned and converting my concepts to standard ones! – Rerito Feb 07 '21 at 09:42
  • That is good for new code, but how would that work for old code, e.g. using std::begin or std::swap ? – Jean-Michaël Celerier Oct 22 '21 at 11:54
  • @Jean-MichaëlCelerier: You don't. If for whatever reason you cannot change that old code, then you're going to have to accept that this will be an implicit requirement of the function, not something you can easily spell out in a concept. – Nicol Bolas Oct 22 '21 at 13:19