如何专门化 std::begin?

12

我正在尝试为自定义容器专门定制std::begin。我这么做是因为我想使用基于范围的for循环来遍历容器。这是我的代码:

class stackiterator { … };
class stack { … };

#include <iterator>

template <> stackiterator std::begin(stack& S)
{
  return S.GetBottom();
}

在我对 begin 进行特化的定义处,我遇到了以下错误:

没有匹配函数模板的函数模板特化 'begin'

我做错了什么?


3
为您的堆栈类创建一个begin成员函数,该函数返回一个迭代器,这样您就不需要模板特化。 - a_pradhan
2
为什么不实现 stack::begin(),并直接使用 std::begin() 的实现呢? - R Sahu
@AkashPradhan 我更喜欢专门化 std::begin,因为[请参见我的先前评论]。 - emlai
1
@zenith,这样你就可以通过ADL找到其他的begin函数。这很混乱,Eric Niebler提出了一个解决方案,使std::begin(和其他自定义点)可以做到这一点,这样我们就不会在函数顶部有一堆重复的using语句,随着自定义点列表的增长而变得越来越大。 - chris
3
我认为标准比编码约定更重要……你应该仔细检查一下是否需要编写成员函数begin。 - Marc Glisse
显示剩余6条评论
3个回答

17
我试图为自定义容器专门化std::begin。我这样做是因为我想使用基于范围的for循环来遍历容器。
你走错了方向。基于范围的for循环根本不使用std::begin。对于类类型,编译器直接查找成员beginend,如果两者都没有找到,则在相关命名空间中进行仅限ADL的查找以获取自由beginend。不进行普通的非限定符查找;如果您的类不在std命名空间中,则无法选择std::begin
即使您想要执行的专门化是可能的(除非您引入一个成员begin() - 函数模板的显式专门化不能更改返回类型,并且所涉及的重载返回“无论成员begin()返回什么”;如果您确实引入了成员begin(),那么为什么要将std::begin专门化以执行它本来会执行的操作?),仍然无法使用基于范围的for循环。

那么,当这个(被接受的)答案说可以通过特化std::begin来实现基于范围的for兼容性时,它是什么意思呢?它只是错了吗? - emlai
@zenith 这已经过时了。 - T.C.

2
略过是否应该从 std 命名空间特化函数模板的政策和语义问题,以下片段不起作用:
class stackiterator {};
struct stack { stackiterator Begin() { return stackiterator{};} };

#include <iterator>

namespace std
{
   template <> stackiterator begin<stack>(stack& S)
   {
      return S.Begin();
   }
}

然而,以下代码片段可以正常运行:
class stackiterator {};
struct stack { stackiterator begin() { return stackiterator{};} };

#include <iterator>

namespace std
{
   template <> stackiterator begin<stack>(stack& S)
   {
      return S.begin();
   }
}

关键的区别在于 stack 的成员函数是 Begin() 还是 begin()。而 std::begin() 的定义如下:
template <class C> auto begin(C& c) -> decltype(c.begin());
template <class C> auto begin(const C& c) -> decltype(c.begin());

当你专门化一个函数模板时,你仍然必须保持返回类型相同。当你没有将begin()作为Stack的成员时,编译器不知道如何确定返回类型。这就是编译器产生错误的原因。
顺便说一下,有另一个SO帖子部分回答了可以专门化什么以及不能专门化什么。
查看处理std::begin()的标准部分24.3,我没有看到任何关于不能专门化std::begin()的内容。

如果您已经有一个成员函数begin(),那么为什么要专门化std::begin()呢? - T.C.
2
@TC,那个评论是针对我还是原帖的? - R Sahu

1
stack的命名空间中添加begin(stack&)begin(stack const&)函数分别返回迭代器和const_iterator(同样适用于end),是实现使for(:)循环能够使用自由函数begin的正确方法。另一种方法是向stack添加成员begin()end()。特化std::begin是不好的做法,有许多原因,其中最重要的是并不是所有的for(:)循环都能与其一起工作(在解决此缺陷报告时更改了查找规则link1)。在namespace std中重载std::begin是未定义行为(您不能在标准下重载namespace std中的函数:这样做会使您的程序非法)。即使违反您项目的命名约定,也必须按照这种方式进行。

2
请注意,它应该是 std::begin 的一个专门化(不是重载),因为这是标准允许的。 - emlai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接