通过using-directive调用begin和end?

时间:2013-09-13 07:09:40

标签: c++ c++11 stl using-directives argument-dependent-lookup

用于调用swap的既定惯用语是:

using std::swap
swap(foo, bar);

这样,swap可以为std命名空间之外的用户定义类型重载。{/ p>

我们应该以同样的方式调用beginend吗?

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

或者我们应该写一下:

some_algorithm(std::begin(some_container), std::end(some_container));

4 个答案:

答案 0 :(得分:9)

使用这样的using - 声明是IMO的正确方法。它也是标准对循环范围的作用:如果没有beginend成员出现,则会调用begin(x)end(x) { {1}}作为关联的命名空间(即,如果ADL找不到非成员stdstd::begin),它会找到std::endbegin

如果您发现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>“创建名称为beginend以及using std::begin; using std::end;。”的函数,相信我,对我来说写作并不乏味,但是很难阅读,阅读冗余。 :p.


我基本上会给你这种技术的可能用例,后来我的结论。

案例1 - 您的beginend方法与标准容器的方法不同

您可能需要重载std::beginstd::end函数的一种情况是,当您以不同的方式使用类型的beginend方法时除了提供对象的元素的类似迭代器的访问,并希望有std::beginstd::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()方法。

因此,这个案例提出的论点是不要使用你提出的建议,因为它会打破一种非常有用的语言特征。

案例2 - beginend方法根本没有定义

另一种情况是您没有定义beginend方法。当您希望将类型扩展为可迭代而不修改类接口时,这是一种更常见且适用的情况。

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:;beginstd::end

结论

由于我没有曾经看到有人做了类似第一种情况的事情(除了我的例子),那么你真的想要使用你提出的内容并重载{{在适用的地方1}}和std::begin


我在这里没有包含您定义std::endbegin方法的情况,以及endbegin函数,它们执行的方法与方法不同。我相信这样的情况是由程序员设计的,形成不良和/或完成的,他们没有多少经验深入研究调试器或阅读新颖的模板错误。

答案 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内)调用一个   不合格的方式允许非基本类型的自定义重载   被调用而不是这个通用版本:交换的自定义重载   声明在与提供它们的类型相同的命名空间中   通过依赖于参数的查找来选择此泛型   版本

beginend的文档中没有此类内容。

出于这个原因,你绝对可以使用

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

调用约定,但您必须知道这是一个不适用于例如约定的约定。标准算法,但仅限于您的代码。