通过 using 指令调用 begin 和 end?

Invoking begin and end via using-directive?

提问人:fredoverflow 提问时间:9/13/2013 更新时间:10/11/2013 访问量:786

问:

调用的既定成语是:swap

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

这样,对于命名空间之外的用户定义类型,可以重载。swapstd

我们是否应该以同样的方式调用?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));
C++ C++11 STL 使用指令 参数依赖查找

评论

1赞 TemplateRex 9/13/2013
对不起,我认为这是一个骗局 stackoverflow.com/q/17562943/819272
0赞 Neil Kirk 10/11/2013
谁来决定既定的成语是什么?
0赞 fredoverflow 10/11/2013
@NeilKirk Effective C++相似书籍

答:

0赞 Stefano Falasca 9/13/2013 #1

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

标准库的许多组件(在 std 中)调用 swap 允许非基本类型自定义重载的不合格方式 被调用而不是这个通用版本:交换的自定义重载 在与提供它们的类型相同的命名空间中声明 通过与参数相关的查找来选择此泛型 版本。

和 的文档中没有这样的东西。beginend

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

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

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

评论

2赞 MSalters 9/13/2013
“cplusplus.com”是一个假设
0赞 Stefano Falasca 9/13/2013
@msalters求你原谅?!
2赞 TemplateRex 9/13/2013
@StefanoFalasca您使用的参考站点已知充满错误。最好使用最新的标准草案,或者至少使用 en.cpp.reference.com
0赞 Stefano Falasca 9/13/2013
@TemplateRex 我想你是对的,非常感谢你让我意识到这一点!不知道。
9赞 Simple 9/13/2013 #2

使用这样的声明是 IMO 的正确方式。这也是标准对 for 循环范围所做的:如果没有 or 成员,那么它将调用 and with 作为关联的命名空间(即,它将找到 and 如果 ADL 找不到非成员和 )。usingbeginendbegin(x)end(x)stdstd::beginstd::endbeginend

如果你觉得一直写很乏味,那么你可以使用下面的和函数:using std::begin; using std::end;adl_beginadl_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);
}

评论

0赞 Simple 9/13/2013
@Useless 您期望的:它返回 返回的类型。仅当 ADL 找不到非成员或 时,将 和 引入作用域的 using 声明才会启动。begin(T)std::beginstd::endbeginend
0赞 Neil Kirk 10/11/2013
这比为 std::begin 多写 1 个字符更好吗?
0赞 Simple 10/11/2013
@NeilKirk因为有时会做错事。std::begin
0赞 Neil Kirk 10/11/2013
他们应该修复语言。这就是人们远离C++的原因。
0赞 Simple 10/11/2013
@NeilKirk 我同意它应该以某种方式修复(我被写这篇文章的两个边缘情况绊倒了)。希望有了 C++14 概念,它变得更容易处理(最终的解决方案)。
1赞 Khurshid Normuradov 9/13/2013 #3

如果您的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
}

评论

0赞 MSalters 9/13/2013
即使是前缀也是不必要的。但是,如果它是一个标准容器,那么它就只能容纳。这里的问题是关于一般情况,其中容器可能是也可能不是标准容器。特别是,不能假设容器必须来自或(全局命名空间),它可能来自任何位置。std::sort::std::::
1赞 Simple 9/13/2013
@MSalters实际上是必需的,因为可以是嵌套命名空间中的指针或类型,在这种情况下,ADL 将找不到 .std::sortstd::vector::iteratorstd::sort
6赞 Mark Garcia 9/13/2013 #4

免責聲明:对于迂腐的类型(或迂腐的类型,如果你想变得迂腐......),我通常在这里将“重载”一词称为“使用 std::begin 创建名称为开头结尾的函数,并执行;使用 std::end;.“,相信我,这对我来说一点也不乏味,但很难阅读,阅读起来是多余的。:p.


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

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

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

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

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

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

另一种情况是未定义 and 方法。这是一种更常见和适用的情况,当您希望在不修改类接口的情况下扩展类型以使其可迭代时。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();
}

这将使您能够在(算法、范围等)上使用一些漂亮的功能,而无需实际修改其界面!这与 Herb Sutter 的建议一致,即通过非成员非友元函数扩展类型的功能。good_ol_type

这是一个很好的情况,你实际上想要重载和 .std:;beginstd::end

结论

由于我从未见过有人做过类似第一种情况的事情(除了我的例子),那么你真的想使用你建议的东西并重载并在适用的情况下。std::beginstd::end


我在这里没有包括您同时定义 and 方法和 和 函数的情况,这些函数的作用与方法不同。我相信这种情况是人为的、格式错误的和/或由没有太多经验深入研究调试器或阅读新模板错误的程序员完成的。beginendbeginend

评论

0赞 Simple 9/13/2013
我明白你想说什么,但超载在这里是错误的术语。不能在命名空间中重载函数。std
0赞 Mark Garcia 9/13/2013
@Simple 感谢您指出这一点。我一定会弄清楚的。再次感谢。