2

I try to overload the operator<< in a namespace. Additionally, I want to include a debug namespace within the first namespace, where the operator<< does more.

In the main function, I create an object of a class within the first namespace and give it out with std::cout. I expected to have to 'name' the operator before I can do that, as in using test::operator<<, but I don't have to.

This leads to my issue: If I want to use my debug operator now, it is ambiguous and I cannot use it and I don't really understand why.

#include <iostream>
#include <string>

namespace test{
    class A{
        std::string str_;
    public:
        explicit A(const std::string& str) : str_{str} {}
        inline std::ostream& toStream(std::ostream& os) const {
            return os << str_ << "\n";
        }
    };
    std::ostream& operator<< (std::ostream& os, const A& a) {
        return a.toStream(os);
    }
}

namespace test {
    namespace debug {
        std::ostream& operator<< (std::ostream& os, const A& a) {
            std::string info = "\n\tDebug\n"
                "\t\tLine: " + std::to_string(__LINE__) + "\n"
                "\t\tFile: " __FILE__ "\n"
                "\t\tDate: " __DATE__ "\n"
                "\t\tTime: " __TIME__ "\n"
                "\t\tVersion: " + std::to_string(__cplusplus) + "\n";
            return a.toStream(os) << info;
        }
    }
}

int main(int argc, const char* argv[]) {
    test::A a{"Test"};
    if(argc > 1) {
        using test::debug::operator<<;
        // Ambiguous error
        std::cout << a << "\n";
    } else {
        // Don't need it for some reason
        // using test::operator<<;
        std::cout << a << "\n";
    }
}
6
  • 2
    can you elaborate why you think it's not ambiguous? they have exactly the same signature. Commented Jun 24, 2020 at 13:34
  • I thought I have to explicitly use using test::operator<<; to tell the compiler where to find the overload, because it is hidden in the namespace "test". Commented Jun 24, 2020 at 13:40
  • The extra debug data will not give you so much though. __FILE__ and __LINE__ will always be the file where the operator<< is defined. I assume you wanted to get the line and file of where you call? Commented Jun 24, 2020 at 13:52
  • Oh, I didn't know that. Yes, correct. I thought the preprocessor uses the file and line where it was called from. Commented Jun 24, 2020 at 13:55
  • No, unfortunately not. That's why logging libraries usually use macros, something like: #define LOG(msg) std::cout << FILE << "(" << LINE << "): " << msg << std::endl . Commented Jun 24, 2020 at 14:02

2 Answers 2

4

When you have:

using test::debug::operator<<;
std::cout << a << "\n";

Lookup for std::cout << a is going to find two candidates:

  • test::debug::operator<<(ostream&, A const&) through regular unqualified lookup, found through the using-declaration.
  • test::operator<<(ostream&, A const&) through argument-dependent lookup (ADL), since A is in namespace test so we find the candidates there.

Those two candidates have the same signature, there's nothing at all to differentiate them, so it's ambiguous.


The sanest thing to do, in my opinion, is actually to wrap a. Write:

std::cout << debug{a} << '\n';

Where debug is just a type that has a member reference to A, and has its own custom logging that is more detailed than usual.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for your anwser. @idclev 463035818's code example helped me a little bit better, that's why I marked his response as the solution, but yours was also very good. I just had to choose one.
What's the difference between using using and defining the operator<< in global namespace? When moved to global namespace, there is no error anymore. godbolt.org/z/CTDkzE
@MikaelH There's only regular, unqualified lookup going on here. We go from innermost to outermost scope until we find a candidate and then we stop. So once you find test::debug::operator<< you don't keep going. ADL finds nothing.
Oh, so it does not have anything to do with namespaces. If we call using, the symbol is pulled into that scope, and it will be found unambiguously? (just like having a global variable int a = 2; redefined in a scope { int a = 3; std::cout << a; // 3})
3

I expected to have to 'name' the operator before I can do that, as in using test::operator<<, but I don't have to.

This is because of Argument Dependent Lookup (ADL).

If you pull the operator from the debug namespace into the current scope via the using, this does not "overwrite" the existing operator, they are both avaliable, hence the ambiguity.

There are many ways to deal with it, one possibility is to use a different type for the debug output:

namespace test {
    namespace debug {
        struct debug_A {
            const A& data;
            debug_out(const A& a) : a(a) {}
        };

        std::ostream& operator<< (std::ostream& os, const debug_A& d) {
            auto& a = d.data;
            std::string info = "\n\tDebug\n"
                "\t\tLine: " + std::to_string(__LINE__) + "\n"
                "\t\tFile: " __FILE__ "\n"
                "\t\tDate: " __DATE__ "\n"
                "\t\tTime: " __TIME__ "\n"
                "\t\tVersion: " + std::to_string(__cplusplus) + "\n";
            return a.toStream(os) << info;
        }
    }
}

Now you can call it via

std::cout << test::debug::debug_A{ a } << '\n';

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.