16

调用的既定习惯用法swap是:

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

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

我们应该以同样的方式调用beginand吗?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));
4

4 回答 4

9

使用这样的using-声明是 IMO 的正确方法。这也是标准对范围 for 循环所做的事情:如果不存在beginorend成员,那么它将调用begin(x)and end(x)with作为关联的命名空间(即,如果 ADL 没有找到非成员and std,它将找到std::begin并且)。std::endbeginend

如果你觉得一直写using std::begin; using std::end;很乏味,那么你可以使用下面的adl_beginandadl_end函数:

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

这段代码非常可怕。希望使用 C++14,这可以变得不那么神秘:

template<typename T>
concept bool Not_array()
{
    using type = std::remove_reference_t<T>;
    return !std::is_array<type>::value;
}

decltype(auto) adl_begin(Not_array&& x)
{
    using std::begin;
    return begin(std::forward<Not_array>(x));
}

decltype(auto) adl_end(Not_array&& x)
{
    using std::end;
    return end(std::forward<Not_array>(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);
}
于 2013-09-13T07:22:57.693 回答
6

免责声明:对于迂腐类型(或迂腐,如果你想迂腐......),我通常将这里的“重载”一词称为“创建具有名称的函数beginandend和 do using std::begin; using std::end;。” ,相信我,我写起来一点也不乏味,但很难读,而且读起来很多余。:p.


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

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

您可能需要重载std::beginandstd::end函数的一种情况是,当您以不同的方式使用您的类型的beginandend方法,而不是提供对对象元素的类似迭代器的访问,并且想要重载std::beginandstd::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();
}

但是,您不会也不应该做这样疯狂的事情,因为如果与 的对象一起使用 range-for 会破坏weird_container,根据 range-for 的规则,weird_container::begin()andweird_container::end()方法将在独立函数变体之前找到。

因此,这种情况带来了不使用您提出的内容的论点,因为它会破坏该语言的一个非常有用的特性。

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

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

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.

结论

因为我从来没有见过有人像第一种情况那样做(除了我的例子),那么你真的想使用你提出的和超载的东西std::begin以及std::end适用的地方。


我没有在这里包括你定义了beginend方法的情况,以及begin与方法end做不同事情的函数。我相信这种情况是人为的、不正确的和/或由一个没有太多经验深入调试器或阅读新模板错误的程序员完成的。

于 2013-09-13T07:52:02.720 回答
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
}
于 2013-09-13T07:32:38.903 回答
0

文档swap指定您引用的成语是 stl 库中的常见做法

标准库的许多组件(在 std 中)以非限定方式调用 swap 以允许调用非基本类型的自定义重载而不是此通用版本:在与其所在类型相同的命名空间中声明的 swap 的自定义重载提供通过在此通用版本上的依赖于参数的查找来选择。

和的文档中没有这样的begin东西end

因此,您绝对可以使用

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

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

于 2013-09-13T07:17:08.313 回答