如何专门化std::begin

How to specialize std::begin?

本文关键字:begin std 专门化      更新时间:2023-10-16

我试图专门为自定义容器std::begin。我这样做是因为我想使用基于范围的for与容器。这是我的文件:

class stackiterator { … };
class stack { … };
#include <iterator>
template <> stackiterator std::begin(stack& S)
{
  return S.GetBottom();
}

我得到以下错误在我的begin专门化的定义:

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

我做错了什么?

我试图将std::begin专门用于定制容器。我这样做是因为我想使用基于范围的for与容器。

你找错对象了。基于范围的for根本不使用std::begin。对于类类型,编译器直接查找成员beginend,如果都没有找到,则在关联的名称空间中查找空闲的beginend。不执行普通的非限定查找;如果您的类不在std名称空间中,则无法拾取std::begin

即使你想要做的特化是可能的(除非你引入了成员begin()——函数模板的显式特化不能改变返回类型,并且有问题的重载返回"任何成员begin()返回";如果你确实引入了成员begin(),为什么你要专门化std::begin来做它本来可以做的事情呢?),你仍然不能将它与基于范围的for一起使用。

不考虑是否应该专门化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();
   }
}

关键的区别在于Begin()begin()作为stack的成员函数存在。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()的任何内容。

添加一个允许for(:)循环的自由函数begin的正确方法是在stack的命名空间中添加一个begin(stack&)begin(stack const&)函数,分别返回一个迭代器和一个const_iterator (end也是如此)

另一种方法是给stack增加成员begin()end()

专门化std::begin是一个不好的实践,原因有很多,其中最重要的是,并不是所有的for(:)循环都可以使用它(查找规则在这个缺陷报告的解析中被改变了)。重载std::begin是一种未定义的行为(你不能在标准下重载namespace std中的函数:这样做会使你的程序形式不良)。

必须这样做,即使它违反了项目的命名约定。