自定义容器是否具有免费的开始/结束功能? [英] Should custom containers have free begin/end functions?

查看:103
本文介绍了自定义容器是否具有免费的开始/结束功能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当创建按照通常规则播放的自定义容器类(即使用STL算法,使用行为良好的通用代码等)时,在C ++ 03中实现迭代器支持和成员begin /结束函数。

When creating a custom container class that plays by the usual rules (i.e. works with STL algorithms, works with well-behaved generic code, etc.), in C++03 it was sufficient to implement iterator support and member begin/end functions.

C ++ 11引入了两个新概念 - 基于范围的for循环和std :: begin / end。基于范围的for循环理解成员开始/结束函数,因此任何C ++ 03容器都支持基于范围的开箱即用。对于算法,推荐的方法(根据Herb Sutter的'Writing modern C ++ code')是使用std :: begin而不是成员函数。

C++11 introduces two new concepts - range-based for loop and std::begin/end. Range-based for loop understands member begin/end functions, so any C++03 containers support range-based for out of the box. For algorithms the recommended way (according to 'Writing modern C++ code' by Herb Sutter) is to use std::begin instead of member function.

然而,在这一点上我不得不问 - 是推荐的方法来调用一个完全限定的begin()函数(即std :: begin(c))还是依赖于ADL并调用begin(c)?

However, at this point I have to ask - is the recommended way to call a fully qualified begin() function (i.e. std::begin(c)) or to rely on ADL and call begin(c)?

ADL在这种特殊情况下似乎没用 - 因为如果可能的话,std :: begin(c)委托给c.begin(),通常的ADL好处似乎不适用。如果每个人都开始依赖ADL,那么所有自定义容器都必须在其必需的命名空间中实现额外的begin()/ end()自由函数。但是,有几个消息来源似乎暗示对开始/结束的无限制调用是推荐的方式(即 https ://svn.boost.org/trac/boost/ticket/6357 )。

ADL seems useless in this particular case - since std::begin(c) delegates to c.begin() if possible, usual ADL benefits do not seem to apply. And if everybody starts to rely on ADL, all custom containers have to implement extra begin()/end() free functions in their requisite namespaces. However, several sources seem to imply that unqualified calls to begin/end are the recommended way (i.e. https://svn.boost.org/trac/boost/ticket/6357).

那么C ++ 11的方式是什么?容器库作者是否应该为其类编写额外的开始/结束函数,以便在没有使用namespace std的情况下支持不合格的开始/结束调用;或使用std :: begin;?

So what is the C++11 way? Should container library authors write extra begin/end functions for their classes to support unqualified begin/end calls in absence of using namespace std; or using std::begin;?

推荐答案

有几种方法,每种方法各有利弊。以下三种方法进行成本效益分析。

There are several approaches, each with their own pros and cons. Below three approaches with a cost-benefit analysis.

第一个替代方案提供非成员 begin() end() legacy 命名空间内的函数模板,用于将所需的功能改进到任何类或类模板上可以提供它,但有例如错误的命名约定。然后,调用代码可以依赖ADL来查找这些新函数。示例代码(基于@Xeo的注释):

The first alternative provides non-member begin() and end() function templates inside a legacy namespace to retrofit the required functionality onto any class or class template that can provide it, but has e.g. the wrong naming conventions. Calling code can then rely on ADL to find these new functions. Example code (based on comments by @Xeo):

// LegacyContainerBeginEnd.h
namespace legacy {

// retro-fitting begin() / end() interface on legacy 
// Container class template with incompatible names         
template<class C> 
auto begin(Container& c) -> decltype(c.legacy_begin())
{ 
    return c.legacy_begin(); 
}

// similarly for begin() taking const&, cbegin(), end(), cend(), etc.

} // namespace legacy

// print.h
template<class C>
void print(C const& c)
{
    // bring into scope to fall back on for types without their own namespace non-member begin()/end()
    using std::begin;
    using std::end;

    // works for Standard Containers, C-style arrays and legacy Containers
    std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and legacy Containers
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

优点一致且简洁的通话完全一般有效的约定


  • 适用于定义成员的任何标准容器和用户类型。 begin() .end()

  • 适用于C风格的数组
  • $对于任何类模板 legacy :: Container< T> b $ b
  • 可以进行改装工作(也适用于 range-for循环!) ; 没有成员 .begin() end()而不需要来源代码修改

  • works for any Standard Container and user-types that define member .begin() and .end()
  • works for C-style arrays
  • can be retrofitted to work (also for range-for loops!) for any class template legacy::Container<T> that does not have member .begin() and end() without requiring source code modifications

缺点:需要在很多地方使用声明

Cons: requires using-declarations in many places


  • std :: begin std :: end 是必需的已被带入每个显式调用范围作为C风格数组的后退选项(模板标题和一般滋扰的潜在缺陷)

  • std::begin and std::end are required to have been brought into every explicit calling scope as fall back options for C-style arrays (potential pitfall for template headers and general nuisance)

第二种方法是通过提供非成员函数模板 adl_begin将先前解决方案的using声明封装到单独的 adl 命名空间中。 ) adl_end(),也可以通过ADL找到。示例代码(基于@Yakk的评论):

A second alternative is to encapsulate the using-declarations of the previous solution into a separate adl namespace by providing non-member function templates adl_begin() and adl_end(), which can then also be found through ADL. Example code (based on comments by @Yakk):

// LegacyContainerBeginEnd.h 
// as before...

// ADLBeginEnd.h
namespace adl {

using std::begin; // <-- here, because otherwise decltype() will not find it 

template<class C> 
auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c)))
{ 
    // using std::begin; // in C++14 this might work because decltype() is no longer needed
    return begin(std::forward<C>(c)); // try to find non-member, fall back on std::
}

// similary for cbegin(), end(), cend(), etc.

} // namespace adl

using adl::adl_begin; // will be visible in any compilation unit that includes this header

// print.h
# include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope

template<class C>
void print(C const& c)
{
    // works for Standard Containers, C-style arrays and legacy Containers
    std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and legacy Containers
    // does not need adl_begin() / adl_end(), but continues to work
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

优点:一致的调用约定完全有效

Pros: consistent calling convention that works completely generically


  • 与@ Xeo的建议相同的优点+

  • 重复使用声明已被封装(干)

缺点:有点冗长


  • adl_begin() / adl_end()不如 begin() / end()

  • 它也许不是惯用的(虽然它是明确的)

  • 待定C ++ 14返回类型推导,也将使用 std :: begin / <$污染命名空间c $ c> std :: end

  • adl_begin() / adl_end() is not as terse as begin() / end()
  • it is perhaps also not as idiomatic (although it is explicit)
  • pending C++14 return type deduction, will also pollute namespace with std::begin / std::end

注意:不确定是否这确实改进了以前的方法。

NOTE: Not sure if this really improves upon the previous approach.

一旦详情 begin() / end()有无论如何都放弃了,为什么不回到 std :: begin()的合格调用 / std :: end()?示例代码:

Once the verbosity of begin() / end() has been given up anyway, why not go back to the qualified calls of std::begin() / std::end()? Example code:

// LegacyIntContainerBeginEnd.h
namespace std {

// retro-fitting begin() / end() interface on legacy IntContainer class 
// with incompatible names         
template<> 
auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin())
{ 
    return c.legacy_begin(); 
}

// similary for begin() taking const&, cbegin(), end(), cend(), etc.

} // namespace std

// LegacyContainer.h
namespace legacy {

template<class T>
class Container
{
public:
    // YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED
    auto begin() -> decltype(legacy_begin()) { return legacy_begin(); }
    auto end() -> decltype(legacy_end()) { return legacy_end(); }

    // rest of existing interface
};

} // namespace legacy

// print.h
template<class C>
void print(C const& c)
{
    // works for Standard Containers, C-style arrays as well as 
    // legacy::IntContainer and legacy::Container<T>
    std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and
    // legacy::IntContainer and legacy::Container<T>
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

优点:几乎一般有效的一致调用约定

Pros: consistent calling convention that works almost generically


  • 适用于定义成员的任何标准容器和用户类型 .begin() .end()

  • 适用于C风格的数组

缺点:有点冗长和改造不是通用的和维护问题

Cons: a little verbose and retrofitting is not generic and a maintainence problem


  • std :: begin() / std :: end()比<$ c更冗长$ c> begin() / end()

  • 只能改装才能工作(也适用于对于没有成员 .begin的任何 LegacyContainer range-for循环!) () end()(并且没有源代码!)通过提供非成员函数模板的显式特化 begin() end() in namespace std

  • 只能通过直接添加成员函数<$ c $来改装到类模板 LegacyContainer< T> c> begin() / end()源代码内部LegacyContainer< T> (适用于模板)。 命名空间std 技巧在这里不起作用,因为函数模板不能部分专门化。

  • std::begin() / std::end() is a little more verbose than begin() / end()
  • can only be retrofitted to work (also for range-for loops!) for any class LegacyContainer that does not have member .begin() and end() (and for which there is no source code!) by providing explicit specializations of the non-member function templates begin() and end() in namespace std
  • can only be retrofitted onto class templates LegacyContainer<T> by directly adding member functions begin() / end() inside the source code of LegacyContainer<T> (which for templates is available). The namespace std trick does not work here because function templates cannot be partially specialized. 

通过非会员的ADL方法 begin() / end()在容器自己的命名空间中是惯用的C ++ 11方法,特别是对于泛型函数这需要对遗留类和类模板进行改造。它与用户提供的非成员 swap()函数的成语相同。

The ADL approach through non-member begin() / end() in a a container's own namespace is the idiomatic C++11 approach, especially for generic functions that require retrofitting on legacy classes and class templates. It is the same idiom as for user-providing non-member swap() functions.

仅适用于代码使用标准容器或C样式数组, std :: begin() std :: end()可能是在不引入使用声明的情况下调用所有地方,代价是更冗长的调用。这种方法甚至可以改装,但它需要摆弄 namespace std (对于类类型)或就地源修改(对于类模板)。它可以完成,但不值得维护麻烦。

For code that only uses Standard Containers or C-style arrays, std::begin() and std::end() could be called everywhere without introducing using-declarations, at the expense of more verbose calls. This approach can even be retrofitted but it requires fiddling with namespace std (for class types) or in-place source modifcations (for class templates). It can be done, but is not worth the maintainence trouble.

在非通用代码中,在编码时知道容器,人们甚至可以依赖仅适用于标准容器的ADL,并为C样式数组显式限定 std :: begin / std :: end 。它失去了一些调用一致性,但节省了使用声明。

In non-generic code, where the container in question is known at coding-time, one could even rely on ADL for Standard Containers only, and explicitly qualify std::begin / std::end for C-style arrays. It loses some calling consistency but saves on using-declarations.

这篇关于自定义容器是否具有免费的开始/结束功能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆