3

I have a weird operator<< overloading problem, for which I can't find a reason. The following code is an extraction of the problem. This code fails to compile using VS2015, g++ 5.4.0 and clang 3.8.0, so I assume it's not a compiler bug.

#include <iostream>
#include <stdexcept>

inline std::ostream &operator<<(std::ostream &o, const std::exception &ex) {
    o << ex.what();
    return o;
}

namespace ns {
    struct Struct { int m; };

    inline std::ostream &operator<<(std::ostream &o, const Struct &s) {
        o << s.m;
        return o;
    }

    void fn() {
        std::cout << Struct{ 1 } << std::endl;
        try {
            throw std::runtime_error("...");
        } catch (std::exception &ex) {
            std::cout << ex << std::endl;
        }
    }
}

int main() {
    return 0;
}

The compiler can't find the overloading of operator<< for std::exception (the line "std::cout << ex << std::endl;" fails). What especially confuses me is, if I either:

  • remove the overloading operator<< for Struct or
  • if I move all code from namespace ns to global namespace

the code compiles. What is the reason for this behavior?

hob-B1T
  • 73
  • 3
  • What stopped you from posting compiler messages/errors as well? – Nawaz Oct 18 '16 at 07:32
  • 1
    @Nawaz: I don't know what stopped the OP, but one reason might be that the error message, when I tried it now in GCC, was 202 lines and 16799 characters. Bjarne couldn't get his overloaded implicit whitespace approved, but C++ certainly has managed to troll us with its error messages... – Thomas Padron-McCarthy Oct 18 '16 at 07:39
  • Ah sorry: VS2015 says: **Severity Code Description Project File Line Suppression State Error C2679 binary '<<': no operator found which takes a right-hand operand of type 'std::exception' (or there is no acceptable conversion)**. – hob-B1T Oct 18 '16 at 07:40
  • 1
    Related: http://stackoverflow.com/questions/5195512/namespaces-and-operator-resolution – grek40 Oct 18 '16 at 07:40
  • @hob-B1T: Post the error message in the question itself. It is *the part* of the question itself. – Nawaz Oct 18 '16 at 07:54

1 Answers1

3

What you're seeing is a consequence of the name lookup rules in C++. This is why it is not recommended to provide operator overloads where there is not at least one operand of a class type that you wrote.

In other words you should design your code so that it does not have operator<<(std::ostream&, std::exception) as it will inadvertantly be hidden by other operator<< inside namespaces from time to time.

This behaviour is the same for all functions, operator names are not a special case.

An alternative solution is to put using ::operator<<; inside any namespace in which you are defining more overloads of operator<<.


The full set of name lookup rules is a bit complicated. The gist of it for unqualified function name lookup is:

  1. argument-dependent lookup searches for the name.
  2. Non-ADL lookup searches for the name.
  3. The results of (1) and (2) are combined to produce the lookup set.

In your code o << s.m, step 1 searches class std::ostream for member operator<<, and namespace std for non-member operator<<.

Step 2 searches:

  • The body of ns::operator<<, in case you had any function declarations inside that function! (nothing found)
  • namespace ns, up to and including the point of the function in which the call occurs. (Found ns::operator<<)

The key point is that once ns::operator<< is found by non-ADL lookup, the non-ADL lookup process stops - it does not continue searching up further enclosing namespace to find more overloads.

The final lookup set is the union of (1) and (2), i.e. std::ostream::operator<<, std::operator<<, and ns::operator<<. Then overload resolution proceeds only on overloads of those qualified names.


Here is a simpler example of the same principle, without the distraction of the ADL set being added in:

void bar(int);

namespace N
{
    void bar(char const *);

    void nfunc() { bar(1); }
}

There is no ADL because the argument 1 is not of class type. Non-ADL unqualified lookup finds N::bar and stops. ::bar is not considered. The code fails to compile.

M.M
  • 138,810
  • 21
  • 208
  • 365