I have code like this
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();
where std::istream_iterator<std::string>
's begin()
and end()
are defined as follows
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>();
}
which is what Mark Nelson has also written about in Dr. Dobb's here. Alas, the code fails to compile on my Visual Studio 2012 with error messages
error C3312: no callable 'begin' function found for type 'std::istream_iterator<_Ty>'
and
error C3312: no callable 'end' function found for type 'std::istream_iterator<_Ty>'
Question: Is there something I haven't noticed, bug in the compiler (unlikely, but just in case) or... Well, any ideas?
This questions is cleaned up considerably, as advised by Xeo. To provide more background and references this is related to my other question on Stackoverflow, I was wondering how to make line based parsing cleaner than the usual loops. A bit of coding and checking from the internet, and I had a working sketch as follows
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();
but there was slight snag I tried to remedy. I think it would look more natural to write as in the code that fails to compile and not like
for(auto& entry : istream_range<std::string>(file)
Please, take a note of the different iterator. The delimeter_tokens is defined like Nawaz kindly has shown here (code not duplicated) and istream_range as in Code Synthesis blog here. I think the begin and end implementations should work, as advertised in the aforementioned Code Synthesis blog post
The last rule (the fallback to the free-standing begin()and end() functions) allows us to non-invasively adapt an existing container to the range-based for loop interface.
Thus my question with all the (ir)relevant background.
Ranged-for relies on ADL if the special handling for native array (T foo[N]
) and member begin
/end
doesn't yield any results.
§6.5.4 [stmt.ranged] p1
- otherwise, begin-expr and end-expr are
begin(__range)
andend(__range)
, respectively, wherebegin
andend
are looked up with argument-dependent lookup (3.4.2). For the purposes of this name lookup, namespacestd
is an associated namespace.
Your problem is, that the associated namespace of std::istream_iterator
is (obviously) namespace std
, not the global namespace.
§3.4.2 [basic.lookup.argdep] p2
For each argument type
T
in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments [...].
- If
T
is a fundamental type, its associated sets of namespaces and classes are both empty.- If
T
is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces of which its associated classes are members. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters [...].
Note the last (quoted) part of the second bullet. It basically means that using a class which is a member of the global namespace as the template argument makes the code work:
#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
;
}
Because of argument depended lookup the compiler tries to find begin()
and end()
in the std
namespace. If you put your functions there, the code compiles.
Since name lookup is a complicated issue in C++ I'm not entirely sure if the compiler is behaving correctly or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With