Reputation: 1903
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.
}
Interesting observations:
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);
}
Upvotes: 4
Views: 185
Reputation: 275405
You can solve this with a bit of indirection.
namespace adl {
namespace std_adl {
using std::begin; using std::end;
template<class T>
auto adl_begin( T&& t )
->decltype(begin(std::forward<T>(t)) )
{ return begin(std::forward<T>(t)); }
template<class T>
auto adl_end( T&& t )
->decltype(end(std::forward<T>(t)) )
{ return end(std::forward<T>(t)); }
}
template<class T>
auto begin(T&& t)
->decltype(std_adl::adl_begin(std::forward<T>(t)))
{ return std_adl::adl_begin(std::forward<T>(t)); }
template<class T>
auto end(T&& t)
->decltype(std_adl::adl_end(std::forward<T>(t)))
{ return std_adl::adl_end(std::forward<T>(t)); }
}
now use adl::begin
in place of std::begin
when you want to enable ADL, both in your has_begin
type trait and at point of use.
As for this:
namespace std {
void begin(foo&) {
}
}
that makes your program ill-formed no diagnostic required.
Upvotes: 3
Reputation: 473447
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.
Upvotes: 6