I found a strange behaviour of C++ resolution of operator-overloading, I can't explain myself. A pointer to some resource describing it would be just as nice as an answer.
I have 2 translation units. In one (called util.cpp/h) I declare and define two operators (I omit the real implementations for readabilty, the problam occurs anyway):
// util.h
#ifndef GUARD_UTIL
#define GUARD_UTIL
#include <iostream>
std::istream& operator>>(std::istream& is, const char* str);
std::istream& operator>>(std::istream& is, char* str);
#endif
And:
//util.cpp
#include "util.h"
#include <iostream>
std::istream& operator>>(std::istream& is, const char* str) {
return is;
}
std::istream& operator>>(std::istream& is, char* str) {
return is;
}
These operators are, if course in global namespace, since they operate on std types and built-in types and should be usable from everywhere. They just work fine from global namespace (e.g. from main()) or with explicitly telling the compiler that they are in global namespace (see code example).
In another translation unit (called test.cpp/h) I use these operators within a namespace. This works until I put a similar operator into this namespace. As soon as this operator is added, the compiler (e.g. gcc or clang) is not able to find a viable operator>> anymore.
// test.h
#ifndef GUARD_TEST
#define GUARD_TEST
#include <iostream>
namespace Namespace {
class SomeClass {
public:
void test(std::istream& is);
};
// without the following line everything compiles just fine
std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; };
}
#endif
And:
//test.cpp
#include "test.h"
#include "util.h"
#include <iostream>
void Namespace::SomeClass::test(std::istream& is) {
::operator>>(is, "c"); //works
is >> "c" //fails
}
Why does the compiler find the correct operator when there is no operator>> in Namespace but fails to find when there is one? Why does the operator affect the ability of the compiler to find the correct one even if it has a different signature?
One attempt to fix this was to put
std::istream& operator>>(std::istream& is, const char* str) { ::operator>>(is, str); }
into Namespace, but than the linker complains about previous definitions. So additional: Why can the linker find something the compiler doesn't find?
This is a name hiding issue. The standard says (c++03, 3.3.7/1)
A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived
class (10.2).
The "name" on your case would be operator>>
and namespaces constitute nested declarative regions.
The easiest way to fix that would be to use a using
declaration where you declare the namespace-local operator<<
:
namespace your_namespece {
std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; };
using ::operator>>;
}
Note that this feature doesn't interfere with Koenig lookup (at least in your case, in principle, it can), so IO operators from std::
will still be found.
PS: Another possibility for working aroud this issue would be defining the operator for SomeClass
as an inline friend
. Such functions are declared at the namespace level (outside of "their" class), but are not visible from there. They can only be found by Koenig lookup.
There are several issues here; for starters, you're redefining a
function in global namespace that already exists in std::
. The
problem you describe, however, is due to the way name lookup works.
Basically, in the case of operator overloading, the compiler does two
name lookups. The first (used for all symbols, not just operators)
starts at the scope where the symbol appears, and works outwards: first
the local blocks, then the class, and its base classes (if any), and
finally the namespaces, working out to the global namespace. An
important characteristic of this lookup is that it stops in whatever
scope it finds the name: if it finds a name in local scope, it doesn't
look in any classes; if it finds one in a class, it doesn't look in the
base classes or namespaces, and if it finds one in a namespace, it
doesn't look in any enclosing namespaces. As far as this lookup is
concerned, all overloads must be in the same scope. The second lookup
only affects functions and operator overloads, and occurs in the context
of classes or objects used as arguments; thus, if one of the operands is
a class in the standard library (or anything derived from a class in the
standard library), the compiler will look for functions in std::
, even
though the context where the symbol is used doesn't include std::
.
The problem you're having is that built-in types, like char*
, don't
imply any namespace (not even global): given your overloads, the first
lookup will stop at the first operator>>
it sees, and the second will
only look in std::
. Your function is in neither. If you want an
overloaded operator to be found, you must define it in the scope of
one of its operands.
Concretely, here: you can't overload std::istream&
operator>>( std::istream&, char* )
, because it is already overloaded in
the standard library. std::istream& operator>>( std::istream&, char
const* )
is possible, but I'm not sure what it's supposed to do, since
it can't write to the second operand. More generally, you should only
overload this operator for types that you have defined, and you should
put your overload in the same namespace as the type itself, so that it
will be found by the second lookup above (called Argument Dependent
Lookup, or ADL—or earlier, Koenig lookup, after the person who
invented it).
:: is global scope, so, compiler must scan global namespace and find this operator.
is >> "C", trying to find operator >> in Namespace, so, compiler find it and stop searching, then compiler try to choose operator with needed signature, if there is no such operator - compiler fails.
I think you should read Herb Sutter Exceptional C++.