为什么没有范围 - 为std :: istream_iterator查找开始和结束的重载?

时间:2012-07-01 13:39:04

标签: c++ visual-c++ c++11 visual-studio-2012 istream-iterator

我有这样的代码

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>的{​​{1}}和begin() 定义如下

end()

Mark Nelson 也在Dobb博士的here中写过。唉,代码无法在我的Visual Studio 2012上编译,并显示错误消息

  

错误C3312:找不到类型为'std :: istream_iterator&lt; _Ty&gt;'的可调用'begin'函数

  

错误C3312:找不到类型'std :: istream_iterator&lt; _Ty&gt;'的可调用'end'函数

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


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

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

但是我试图解决这个问题。我认为编写代码无法编译而不喜欢

会更自然
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();

请注意不同的迭代器。 delimeter_tokens 定义为other question,如代码合成博客Nawaz中显示here(代码未重复)和 istream_range 。我认为开始和结束实现应该有效,正如前面提到的代码综合博客文章中所宣传的那样

最后一条规则(对独立的begin()和end()函数的回退)允许我们非-invasively使现有容器适应基于范围的for循环接口。

因此我的问题与所有(红色)相关背景。

2 个答案:

答案 0 :(得分:7)

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

§6.5.4 [stmt.ranged] p1

  
      
  • 否则, begin-expr end-expr 分别为begin(__range)end(__range)其中begin并使用参数依赖查找(3.4.2)查找end。出于此名称查找的目的,名称空间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
    ;
}

答案 1 :(得分:1)

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

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