为非数组重载 std::begin() 和 std::end()
Overloading std::begin() and std::end() for non-arrays
假设我有以下 Data
class:
struct Data {
char foo[8];
char bar;
};
和下面的函数,my_algorithm
,需要一对char *
(类似于STL算法):
void my_algorithm(char *first, char *last);
对于Data
的foo
数据成员,而不是像这样调用my_algorithm()
:
Data data;
my_algorithm(data.foo, data.foo + 8);
我可以使用 std::begin()
and std::end()
便捷函数模板:
my_algorithm(std::begin(data.foo), std::end(data.foo));
我想实现类似于 Data
的 bar
数据成员的东西。也就是说,而不是写:
my_algorithm(&data.bar, &data.bar + 1);
我想写这样的东西:
my_algorithm(begin(data.bar), end(data.bar));
因此,我为这种情况定义了以下两个普通(非模板)函数:
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
这样我就可以编写如下代码:
Data data;
using std::begin;
using std::end;
my_algorithm(begin(data.foo), end(data.foo)); // ok - std::begin()/std::end()
my_algorithm(begin(data.bar), end(data.bar)); // Error!!!
使用上面的 using
声明,我预计 std::begin()
/std::end()
和 ::begin()
/::end()
分别位于同一个重载集中.由于函数 ::begin()
和 ::end()
是后一个调用的完美匹配,而且它们不是模板,所以我希望最后一次调用 my_algorithm()
能够匹配它们。但是,根本不考虑普通功能。结果编译失败,因为 std::begin()
和 std::end()
与调用不匹配。
基本上,后一个调用就像我写的一样:
my_algorithm(begin<>(data.bar), end<>(data.bar));
也就是说,重载决策过程只考虑函数模板(即std::begin()
/std::end()
),而不考虑普通函数(即不::begin()
/::end()
).
如果我完全限定对 ::begin()
/::end()
:
的调用,它只会按预期工作
my_algorithm(::begin(data.bar), ::end(data.bar));
我在这里错过了什么?
问题的标题是“Overloading std::begin()”。重载只能在同一范围内进行。那就是你不能重载来自不同范围的名称。在另一个范围内,我们只能努力帮助查找名称。本质上,这里 "using std::begin" 声明隐藏了 ::begin 问题代码。参考S.Lippman:
functions that are members of two distinct namespaces do not overload one another.
Scope of a using Declaration. Names introduced in a using declaration obey normal scope rules.
Entities with the same name defined in an outer scope are hidden.
只要参数是 char 并且 char 是基本类型 - 不应考虑参数依赖查找 - 如评论中所述 - 没有与基本类型相关联的命名空间。
同样,问题是:"What am I missing?" - 因此答案仅关注原因 - 建议可能过于宽泛。
让我们得到一个完整的、可重现的例子:
#include <iterator>
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
namespace ns {
void my_algorithm(char *first, char *last);
void my_function() {
using std::begin;
using std::end;
char c = '0';
my_algorithm(begin(c), end(c));
}
}
当您对 begin(c)
和 end(c)
进行非限定调用时,编译器会经历 unqualified name lookup (described on the Argument-dependent lookup page of cppreference).
的过程
对于常规的非限定名称查找,该过程大致是从您当前所在的命名空间开始——::ns
在这种情况下——只有在找不到特定名称时才移出一个命名空间。
如果一个函数调用是不合格的,就像这里的 begin(c)
和 end(c)
,argument dependent lookup 可能会发生,它会找到在与类型相同的名称空间中声明的自由函数函数的参数,通过查找 "associated namespaces."
扩展重载集的过程
但是,在这种情况下,char
是基本类型,因此依赖于参数的查找不允许我们找到全局 ::begin
和 ::end
函数。
For arguments of fundamental type, the associated set of namespaces and classes is empty
cppreference: argument dependent lookup
相反,因为我们已经有了 using std::begin; using std::end;
,编译器已经看到了 begin(...)
和 end(...)
的可能函数——即那些定义在命名空间 ::std
中的函数——而不必将命名空间从 ::ns
移出到 ::
。因此,编译器使用这些函数,编译失败。
值得注意的是,using std::begin; using std::end;
也会阻止编译器找到自定义 ::begin
和 ::end
,即使您将它们放在 ::ns
中也是如此。
您可以改为编写自己的 begin
和 end
:
#include <iterator>
namespace ns {
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
template <typename T>
auto begin(T&& t) {
using std::begin;
// Not unbounded recursion if there's no `std::begin(t)`
// or ADL `begin(t)`, for the same reason that our
// char* begin(char& c); overload isn't found with
// using std::begin; begin(c);
return begin(t);
}
template <typename T>
auto end(T&& t) {
using std::end;
return end(t);
}
void my_algorithm(char *first, char *last);
void my_function() {
char c = '0';
my_algorithm(ns::begin(c), ns::end(c));
}
}
假设我有以下 Data
class:
struct Data {
char foo[8];
char bar;
};
和下面的函数,my_algorithm
,需要一对char *
(类似于STL算法):
void my_algorithm(char *first, char *last);
对于Data
的foo
数据成员,而不是像这样调用my_algorithm()
:
Data data;
my_algorithm(data.foo, data.foo + 8);
我可以使用 std::begin()
and std::end()
便捷函数模板:
my_algorithm(std::begin(data.foo), std::end(data.foo));
我想实现类似于 Data
的 bar
数据成员的东西。也就是说,而不是写:
my_algorithm(&data.bar, &data.bar + 1);
我想写这样的东西:
my_algorithm(begin(data.bar), end(data.bar));
因此,我为这种情况定义了以下两个普通(非模板)函数:
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
这样我就可以编写如下代码:
Data data;
using std::begin;
using std::end;
my_algorithm(begin(data.foo), end(data.foo)); // ok - std::begin()/std::end()
my_algorithm(begin(data.bar), end(data.bar)); // Error!!!
使用上面的 using
声明,我预计 std::begin()
/std::end()
和 ::begin()
/::end()
分别位于同一个重载集中.由于函数 ::begin()
和 ::end()
是后一个调用的完美匹配,而且它们不是模板,所以我希望最后一次调用 my_algorithm()
能够匹配它们。但是,根本不考虑普通功能。结果编译失败,因为 std::begin()
和 std::end()
与调用不匹配。
基本上,后一个调用就像我写的一样:
my_algorithm(begin<>(data.bar), end<>(data.bar));
也就是说,重载决策过程只考虑函数模板(即std::begin()
/std::end()
),而不考虑普通函数(即不::begin()
/::end()
).
如果我完全限定对 ::begin()
/::end()
:
my_algorithm(::begin(data.bar), ::end(data.bar));
我在这里错过了什么?
问题的标题是“Overloading std::begin()”。重载只能在同一范围内进行。那就是你不能重载来自不同范围的名称。在另一个范围内,我们只能努力帮助查找名称。本质上,这里 "using std::begin" 声明隐藏了 ::begin 问题代码。参考S.Lippman:
functions that are members of two distinct namespaces do not overload one another.
Scope of a using Declaration. Names introduced in a using declaration obey normal scope rules. Entities with the same name defined in an outer scope are hidden.
只要参数是 char 并且 char 是基本类型 - 不应考虑参数依赖查找 - 如评论中所述 - 没有与基本类型相关联的命名空间。 同样,问题是:"What am I missing?" - 因此答案仅关注原因 - 建议可能过于宽泛。
让我们得到一个完整的、可重现的例子:
#include <iterator>
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
namespace ns {
void my_algorithm(char *first, char *last);
void my_function() {
using std::begin;
using std::end;
char c = '0';
my_algorithm(begin(c), end(c));
}
}
当您对 begin(c)
和 end(c)
进行非限定调用时,编译器会经历 unqualified name lookup (described on the Argument-dependent lookup page of cppreference).
对于常规的非限定名称查找,该过程大致是从您当前所在的命名空间开始——::ns
在这种情况下——只有在找不到特定名称时才移出一个命名空间。
如果一个函数调用是不合格的,就像这里的 begin(c)
和 end(c)
,argument dependent lookup 可能会发生,它会找到在与类型相同的名称空间中声明的自由函数函数的参数,通过查找 "associated namespaces."
但是,在这种情况下,char
是基本类型,因此依赖于参数的查找不允许我们找到全局 ::begin
和 ::end
函数。
For arguments of fundamental type, the associated set of namespaces and classes is empty
cppreference: argument dependent lookup
相反,因为我们已经有了 using std::begin; using std::end;
,编译器已经看到了 begin(...)
和 end(...)
的可能函数——即那些定义在命名空间 ::std
中的函数——而不必将命名空间从 ::ns
移出到 ::
。因此,编译器使用这些函数,编译失败。
值得注意的是,using std::begin; using std::end;
也会阻止编译器找到自定义 ::begin
和 ::end
,即使您将它们放在 ::ns
中也是如此。
您可以改为编写自己的 begin
和 end
:
#include <iterator>
namespace ns {
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
template <typename T>
auto begin(T&& t) {
using std::begin;
// Not unbounded recursion if there's no `std::begin(t)`
// or ADL `begin(t)`, for the same reason that our
// char* begin(char& c); overload isn't found with
// using std::begin; begin(c);
return begin(t);
}
template <typename T>
auto end(T&& t) {
using std::end;
return end(t);
}
void my_algorithm(char *first, char *last);
void my_function() {
char c = '0';
my_algorithm(ns::begin(c), ns::end(c));
}
}