c++ - std::begin - 类型特征中不考虑用户定义的重载

标签 c++ templates c++17 typetraits

考虑以下类型特征:

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 {};

为什么此特征不考虑我为 std::begin 定义的重载?

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

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

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

Live example

有趣的观察:

  1. 如果我更改此类型特征和重载的顺序,它就会起作用
  2. 如果我在类型特征中使用 ADL:

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

)

如果自由函数 beginfoo 位于同一命名空间中,则它可以工作:

void begin(foo) {
}

但对于 std:: 之外的任何类都会失败,具体取决于:

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

因为 ADL 查找不适用于其他命名空间的模板。

<小时/>

如何在不更改包含顺序的情况下支持类型特征中的 std::begin(foo&)

否则我必须支持两个世界 - 为 std::begin 和 ADL begin() 编写类型特征...

在我的函数中,我已经做了类似的事情(建议 here ):

auto get_begin() {
  using std::begin;
  return begin(object);
}

最佳答案

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

你没有; std::begin 并不意味着直接调用任意范围。如果您想访问范围类型的 begin/end,则应该使用 ADL,并结合 using std::begin/end。这正是该习惯用法在 C++ 中的工作原理。

std 命名空间中重载方法是非法的,std::begin 也不异常(exception)。您可以创建 std 定义模板的模板特化(基于用户创建的类型),但这不是使用 C++ 习惯用法的正确方法。

在 C++20 中,std::ranges::begin 函数应该直接调用,而将其专门化为类型的方式是通过 ADL 或成员 开始 函数。所以用这个成语就行了,大家就都没事了。

关于c++ - std::begin - 类型特征中不考虑用户定义的重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58894939/

相关文章:

c++ - 将许多函数声明为类的友元

c++ - 为什么 std::optional 不允许对 "move construct and copy assign only"类型进行 move 赋值?

c++ - 写入未对齐的存储

constructor - Constexpr 隐式声明的函数

c++ - 有什么方法可以从 C++ 中的成员指针类型派生对象类型

c++ - GetThreadContext() 返回错误 18 - 没有更多文件

c++ - 为什么 MSCVRT 库会在链接时产生冲突?

c# - 如何按参数化类型过滤而不考虑其参数类型?

c++ - std::enable_if :参数与模板参数

c++ - 常量方法中的奇怪行为,可以修改变量