11

我有这样的代码

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>();
}

这也是Mark Nelson在 Dobb 博士的文章中所写的内容。唉,代码无法在我的 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)

请注意不同的迭代器。demeter_tokens的定义就像Nawaz在这里展示的那样(代码不重复),而istream_range的定义就像代码综合博客中的一样。我认为 begin 和 end 实现应该可以工作,正如前面提到的代码合成博客文章中所宣传的那样

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

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

4

2 回答 2

8

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

§6.5.4 [stmt.ranged] p1

  • 否则,begin-exprend-expr分别是begin(__range)end(__range)其中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
    ;
}
于 2012-07-01T14:14:00.913 回答
1

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

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

于 2012-07-01T13:56:12.507 回答