通过using-directive调用begin和end?

fre*_*low 16 c++ stl using-directives argument-dependent-lookup c++11

已经确定的调用习惯swap是:

using std::swap
swap(foo, bar);
Run Code Online (Sandbox Code Playgroud)

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

我们应该调用begin,并end以同样的方式?

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));
Run Code Online (Sandbox Code Playgroud)

或者我们应该写:

some_algorithm(std::begin(some_container), std::end(some_container));
Run Code Online (Sandbox Code Playgroud)

Sim*_*ple 9

使用using- 这样的声明是IMO的正确方法.这也是什么标准并与循环范围:如果没有beginend存在,那么它会调用成员begin(x),并end(x)std作为关联的命名空间(即它会找到std::begin并且std::end如果ADL没有找到非会员beginend).

如果您发现写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);
}
Run Code Online (Sandbox Code Playgroud)

这段代码非常可怕.希望通过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);
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*cia 6

免责声明:对于迂腐类型(或悬挂物,如果你想成为迂腐......),我通常是指词"过载"这里"创建有名称的功能beginendusing std::begin; using std::end;." 相信我,对我来说,写作并不乏味,但阅读起来很难阅读.:p.


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

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

一个情况下,你可能需要重载std::beginstd::end功能是当你使用begin,并end在其他不同的方式,而不是提供一个对象的元素类迭代器的访问,并希望有重载你的类型的方法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();
}
Run Code Online (Sandbox Code Playgroud)

但是,你不会也不应该做如此疯狂的事情,因为如果与一个对象一起使用,它将会破坏weird_container,根据范围规则,for weird_container::begin()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();
}
Run Code Online (Sandbox Code Playgroud)

这将使您能够使用一些漂亮的功能good_ol_type(算法,范围等),而无需实际修改其界面!这符合Herb Sutter关于通过非成员非朋友功能扩展类型功能的建议.

这是很好的情况下,一个在那里你真的想超载std:;beginstd::end.

结论

由于我没有以往见过有人做这样的事情在第一种情况下(除了我的例子),那么你真的想使用你提出什么,过载std::beginstd::end(如适用).


我没有在这里列出你定义了两者beginend方法的情况,以及begin与方法end不同的函数.我相信这样的情况是由程序员设计的,形成不良和/或完成的,他们没有多少经验深入研究调试器或阅读新颖的模板错误.