用于调用swap
的既定惯用语是:
using std::swap
swap(foo, bar);
这样,swap
可以为std
命名空间之外的用户定义类型重载。{/ p>
我们应该以同样的方式调用begin
和end
吗?
using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));
或者我们应该写一下:
some_algorithm(std::begin(some_container), std::end(some_container));
答案 0 :(得分:9)
使用这样的using
- 声明是IMO的正确方法。它也是标准对循环范围的作用:如果没有begin
或end
成员出现,则会调用begin(x)
和end(x)
{ {1}}作为关联的命名空间(即,如果ADL找不到非成员std
和std::begin
),它会找到std::end
和begin
。
如果您发现end
一直写作乏味,那么您可以使用以下using std::begin; using std::end;
和adl_begin
函数:
adl_end
这段代码非常可怕。希望通过C ++ 14,这可以变得不那么神秘了:
namespace aux {
using std::begin;
using std::end;
template<class T>
auto adl_begin(T&& x) -> decltype(begin(std::forward<T>(x)));
template<class T>
auto adl_end(T&& x) -> decltype(end(std::forward<T>(x)));
template<class T>
constexpr bool is_array()
{
using type = typename std::remove_reference<T>::type;
return std::is_array<type>::value;
}
} // namespace aux
template<class T,
class = typename std::enable_if<!aux::is_array<T>()>::type>
auto adl_begin(T&& x) -> decltype(aux::adl_begin(std::forward<T>(x)))
{
using std::begin;
return begin(std::forward<T>(x));
}
template<class T,
class = typename std::enable_if<!aux::is_array<T>()>::type>
auto adl_end(T&& x) -> decltype(aux::adl_end(std::forward<T>(x)))
{
using std::end;
return end(std::forward<T>(x));
}
template<typename T, std::size_t N>
T* adl_begin(T (&x)[N])
{
return std::begin(x);
}
template<typename T, std::size_t N>
T* adl_end(T (&x)[N])
{
return std::end(x);
}
答案 1 :(得分:6)
免责声明:对于迂腐的类型(或小学生,如果你想迂腐...),我通常会在这里提到“overload”这个词为< em>“创建名称为begin
和end
以及using std::begin; using std::end;
。”的函数,相信我,对我来说写作并不乏味,但是很难阅读,阅读冗余。 :p.
我基本上会给你这种技术的可能用例,后来我的结论。
begin
和end
方法与标准容器的方法不同您可能需要重载std::begin
和std::end
函数的一种情况是,当您以不同的方式使用类型的begin
和end
方法时除了提供对象的元素的类似迭代器的访问,并希望有std::begin
和std::end
的重载调用用于迭代的开始和结束方法。
struct weird_container {
void begin() { std::cout << "Start annoying user." }
void end() { std::cout << "Stop annoying user." }
iterator iter_begin() { /* return begin iterator */ }
iterator iter_end() { /* return end iterator */ }
};
auto begin(weird_container& c) {
return c.iter_begin();
}
auto end(weird_container& c) {
return c.iter_end();
}
然而,根据范围规则weird_container
的规定,如果与weird_container::begin()
的对象一起使用,则不会也不应该像range-for那样疯狂。在独立函数变体之前可以找到weird_container::end()
方法。
因此,这个案例提出的论点是不要使用你提出的建议,因为它会打破一种非常有用的语言特征。
begin
和end
方法根本没有定义另一种情况是您没有定义begin
和end
方法。当您希望将类型扩展为可迭代而不修改类接口时,这是一种更常见且适用的情况。
struct good_ol_type {
...
some_container& get_data();
...
};
auto begin(good_ol_type& x) {
return x.get_data().begin();
}
auto end(good_ol_type& x) {
return x.get_data().end();
}
这将使您能够在good_ol_type
(算法,范围等)上使用一些漂亮的功能,而无需实际修改其界面!这符合Herb Sutter关于通过非成员非朋友功能扩展类型功能的建议。
这是一个很好的案例,你实际上想要重载std:;begin
和std::end
。
由于我没有曾经看到有人做了类似第一种情况的事情(除了我的例子),那么你真的想要使用你提出的内容并重载{{在适用的地方1}}和std::begin
。
我在这里没有包含您定义std::end
和begin
方法的情况,以及end
和begin
函数,它们执行的方法与方法不同。我相信这样的情况是由程序员设计的,形成不良和/或完成的,他们没有多少经验深入研究调试器或阅读新颖的模板错误。
答案 2 :(得分:1)
如果你的some_container是标准容器,std ::前缀是不必要的
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
std::vector<int>v { 1, 7, 1, 3, 6, 7 };
std::sort( begin(v), end(v) ); // here ADL search finds std::begin, std::end
}
答案 3 :(得分:0)
swap
的{{3}}指定您引用的习惯用法是stl库中的常用做法
标准库的许多组件(在std内)调用一个 不合格的方式允许非基本类型的自定义重载 被调用而不是这个通用版本:交换的自定义重载 声明在与提供它们的类型相同的命名空间中 通过依赖于参数的查找来选择此泛型 版本
begin
和end
的文档中没有此类内容。
出于这个原因,你绝对可以使用
using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));
调用约定,但您必须知道这是一个不适用于例如约定的约定。标准算法,但仅限于您的代码。