Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::begin - User-defined overloads not considered in type traits

Consider following type trait:

template<typename T, typename = void>
struct has_begin : std::false_type {};

template<typename T>
struct has_begin<T, std::void_t<
    decltype(std::begin(std::declval<std::add_lvalue_reference_t<T>>()))
  >> : std::true_type {};

Why does this trait does not consider my user-defined overload for std::begin?

namespace std {
void begin(foo&) {}
}

int main() {
  static_assert(has_begin<foo>::value); // Fails.

  foo f;
  std::begin(f); // Works.
}

Live example

Interesting observations:

  1. if I change the order of this type trait and my overload, it works
  2. if I use ADL in the type trait:

decltype(std::begin(std::add_lva... -> decltype(begin(std::add_lva...

it works if the free function begin is in the same namespace as foo:

void begin(foo) {
}

but fails for any class outside std:: depending on:

template<class C>
auto begin(C& c) -> decltype(c.begin());

because ADL lookup does not work with templates from other namespaces.


What could I do to support std::begin(foo&) in my type trait without changing the include order?

Otherwise I have to support both worlds - writing the type trait for std::begin and ADL begin()...

In my functions I already do something like this (suggested here):

auto get_begin() {
  using std::begin;
  return begin(object);
}
like image 690
Viatorus Avatar asked Dec 31 '22 12:12

Viatorus


1 Answers

What could I do to support std::begin(foo&) in my type trait without changing the include order?

You don't; std::begin is not meant to be called directly for arbitrary ranges. If you want to access begin/end for a range type, you're supposed to use ADL, combined with using std::begin/end. That's just how the idiom works in C++.

It is illegal to overload methods in the std namespaces, and std::begin is no exception. You can create a template specializations of std-defined templates (based on user-created types), but this is not the proper way to use the C++ idiom.

In C++20, the std::ranges::begin function is meant to be called directly, and the way you specialize it for a type is through ADL or a member begin function. So just use the idiom, and everyone will be fine.

like image 125
Nicol Bolas Avatar answered Feb 08 '23 11:02

Nicol Bolas