为什么 range-for 找不到 std::istream_iterator 的开始和结束重载?

Why doesn't range-for find my overloads of begin and end for std::istream_iterator?

本文关键字:开始 结束 重载 找不到 range-for std istream 为什么 iterator      更新时间:2023-10-16

我有这样的代码

std::ifstream file(filename, std::ios_base::in);
if(file.good())
{
    file.imbue(std::locale(std::locale(), new delimeter_tokens()));
    for(auto& entry : std::istream_iterator<std::string>(file))
    {
        std::cout << entry << std::endl;    
    }
}
file.close();

std::istream_iterator<std::string>begin()end()定义如下

template<class T>
std::istream_iterator<T> begin(std::istream_iterator<T>& stream)
{
    return stream;
}
template<class T>
std::istream_iterator<T> end(std::istream_iterator<T>& stream)
{
    return std::istream_iterator<T>();
}

这也是马克·纳尔逊在多布博士的《这里》中所写的。唉,代码无法在我的 Visual Studio 2012 上编译并显示错误消息

错误 C3312:找不到类型"std::istream_iterator<_Ty>"的可调用"开始"函数

错误 C3312:找不到类型"std::istream_iterator<_Ty>"的可调用"结束"函数

问题:是否有我没有注意到的东西,编译器中的错误(不太可能,但以防万一)或......嗯,有什么想法吗?


根据Xeo的建议,这个问题得到了相当大的清理。为了提供更多的背景和参考,这与我在 Stackoverflow 上的另一个问题有关,我想知道如何使基于行的解析比通常的循环更干净。从互联网上进行了一些编码和检查,我有一个工作草图如下

std::ifstream file(filename, std::ios_base::in);
if(file.good())
{               
    file.imbue(std::locale(std::locale(), new delimeter_tokens()));
    for(auto& entry : istream_range<std::string>(file)
    {
        std::cout << entry << std::endl;    
    }
}
file.close();

但是我试图补救有轻微的障碍。我认为在编译失败的代码中编写看起来会更自然,而不是像

for(auto& entry : istream_range<std::string>(file)

请记下不同的迭代器。delimeter_tokens的定义就像 Nawaz 在这里善意展示的那样(代码不重复),istream_range如这里的代码合成博客所示。我认为开始和结束实现应该有效,正如前面提到的代码合成博客文章

最后一条规则(回退到独立的 begin() 和 end() 函数)允许我们非侵入性地将现有容器适应基于范围的 for 循环接口。

因此,我的问题具有所有(不)相关的背景。

如果对本机数组(T foo[N])和成员begin/end的特殊处理没有产生任何结果,则Ranged-for依赖于ADL。

§6.5.4 [stmt.ranged] p1

  • 否则,分别begin(__range) end(__range)BEGIN-EXPREND-EXPR其中 beginend 使用依赖于参数的查找进行查找 (3.4.2)。出于此名称查找的目的,命名空间std是关联的命名空间。

您的问题是,std::istream_iterator的关联命名空间(显然)是namespace std,而不是全局命名空间。

§3.4.2 [basic.lookup.argdep] p2

对于函数调用中T的每个参数类型,需要考虑一组零个或多个关联的命名空间和一组零个或多个关联的类。命名空间和类的集合完全由函数参数的类型 [...] 决定。

  • 如果T是基本类型,则其关联的命名空间和类集均为空。
  • 如果T是类类型(包括联合),则其关联的类是:类本身;它是其成员的类(如果有的话);及其直接和间接基类。其关联的命名空间是其关联类所属的命名空间。此外,如果 T 是类模板专用化,则其关联的命名空间和类还包括:与为模板类型参数提供的模板参数的类型关联的命名空间和类 [...]。

请注意第二个项目符号的最后一部分(引用)。这基本上意味着使用作为全局命名空间成员的类作为模板参数可以使代码工作:

#include <iterator>
#include <iostream>
template<class T>
std::istream_iterator<T> begin(std::istream_iterator<T> is){
  return is;
}
template<class T>
std::istream_iterator<T> end(std::istream_iterator<T>){
  return std::istream_iterator<T>();
}
struct foo{};
std::istream& operator>>(std::istream& is, foo){
  return is;
}
int main(){
  for(foo f : std::istream_iterator<foo>(std::cin))
  //                                ^^^
  // make global namespace one of the associated namespaces
    ;
}

由于依赖于参数的查找,编译器尝试在std命名空间中查找begin()end()。如果你把你的函数放在那里,代码就会编译。

由于名称查找在C++中是一个复杂的问题,因此我不完全确定编译器的行为是否正确。