2

I'm reading a book. Obviously there are some failures in it.

In the code below, how can I access plant::operator<< in run() and plant::debug::operator<< in diagnostic()? The compiler can't decide between multiple operator<< in method diagnostic(). How can I tell it which one to use?

#include <iostream>
#include <string>

namespace plant {
    class Tree
    {
        std::string name_;
        public:
        explicit Tree(const std::string_view name) : name_{name} {}
        void print(std::ostream& os) const { os << name_; }
 
    };

    std::ostream& operator<<(std::ostream& os, const Tree& arg)
    {
        arg.print(os); return os;
    }
    namespace examples {

        }
    namespace debug {
        std::ostream& operator<<(std::ostream& os, const Tree& arg)
        {
            os << "DEBUG: .."; arg.print(os); return os;
        }    
    }
}

plant::Tree baum{"Mein Baum"};
void run()
{
    using namespace plant;
    std::cout << baum << "\n";
}

void diagnostic()
{
    using namespace plant::debug;
    std::cout << baum << "\n";
}

int main() {
    run();
    diagnostic();
}
4
  • 4
    debug::operator<<(std::cout, baum); ? Commented May 14 at 18:09
  • 5
    Something of an XY problem here. If you want two different ways to print a type, defining the sameoperator<< in different namespaces is probably not the correct start. Commented May 14 at 18:15
  • Side note: The streaming operators often work "better" as friend functions rather than member functions. Explanation of why linked here. More notes on Operator overloads linked here. Commented May 14 at 18:54
  • First link in previous comment is still live, but does not allow you to proceed to the next page of the article. Here's a PDF of the print version so you can read the whole thing: aristeia.com/Papers/CUJ_Feb_2000.pdf Commented May 14 at 19:44

3 Answers 3

8

One way to break the tie could be to not stream your Tree object directly but to create an adapter, here called debug:

namespace plant {
template <class T>
struct debug {
    const T& obj;

    friend std::ostream& operator<<(std::ostream& os, const debug<T>& arg) {
        os << "DEBUG: ..";
        return os << arg.obj;
    }
};
} // namespace plant

void diagnostic() {
    using namespace plant;
    std::cout << debug{baum} << "\n";
}

Demo

You could also specialize it for different types in the plant namespace if needed - or move it to a more generic namespace in case you want it used for types in other namespaces too.

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

1 Comment

This answer has a "say-what-you-mean" flavor that I like.
6

Inside of diagnostic(), both operator<<'s are found as candidates, hence the ambiguity. plant::operator<< is found due to Argument-Dependent Lookup (which makes the using in run() redundant), and plant::debug::operator<< is found due to its using statement.

To resolve the ambiguity, you could call the debug::operator<< directly, eg:

void diagnostic()
{
    //using namespace plant::debug;
    plant::debug::operator<<(std::cout, baum) << "\n";
}

However, that kind of defeats the purpose of defining an operator<< in the first place, rather than a standalone function.

One option is you can instead simply move plant::operator<< into its own namespace, and then update run() to use that namespace, eg:

namespace plant {
    class Tree
    {
        ...
    };

    namespace no_debug {
        std::ostream& operator<<(std::ostream& os, const Tree& arg)
        {
            ...
            return os;
        }    
    }
    namespace debug {
        std::ostream& operator<<(std::ostream& os, const Tree& arg)
        {
            os << "DEBUG: ..";
            ...
            return os;
        }    
    }
}

...

void run()
{
    using namespace plant::no_debug;
    std::cout << baum << "\n";
}

void diagnostic()
{
    using namespace plant::debug;
    std::cout << baum << "\n";
}

Online Demo

1 Comment

Using using namespace to import an operator is unreliable, because it brings it to the common enclosing namespace of yours and the one you're importing, which makes it prone to shadowing by other unrelated operators in your namespace. Works fine in the toy examples, but might break in practice. The individual operator has to be using-ed instead.
3

The reason for the ambiguity is that you've defined the same operator with same overloads once in the plant namespace and once in its nested plant::debug namespace.

To solve this you have three option:

  1. Call the desired operator explicitly, like plant::debug::operator<<(std::cout, baum)
  2. Avoid overlaping by moving plant::operator<< to it's own namespace, like plant::regular::operator<<
  3. Make a clear distinction between the overloads

You can make the overloads distinct using a wrapper, here is an example based on your code:

#include <iostream>
#include <string>

namespace plant {
    class Tree
    {
        std::string name_;
        public:
        explicit Tree(const std::string_view name) : name_{name} {}
        void print(std::ostream& os) const { os << name_; }

    };

    std::ostream& operator<<(std::ostream& os, const Tree& arg)
    {
        arg.print(os); return os;
    }

    namespace debug {
        struct DebugTree {
            const Tree& t;
        };
        DebugTree debug(const Tree& t) { return DebugTree{t}; }

        std::ostream& operator<<(std::ostream& os, DebugTree dbg) {
            os << "DEBUG: ..";  dbg.t.print(os); return os;
        }
    }
}

plant::Tree baum{"Mein Baum"};
void run()
{
    std::cout << baum << "\n";
}

void diagnostic()
{
    std::cout << plant::debug::debug(baum) << "\n";
}

int main() {
    run();
    diagnostic();
}

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.