Elegant way to prevent namespace poisoning in C++

2019-02-20 09:27发布

问题:

Let's assume, Bob has wrapped his library into the namespace "bob", and Alice is going to make the whole namespace visible inside her own function by a single "using namespace bob", instead of "using bob::XYZ" for every single item:

// This file is written by Alice:
#include <iostream>

// She uses Bobs library:
#include "bob.hpp"

int main(void) {
    // Import Bobs library and use it:
    using namespace bob;

    unsigned short value = 50000;
    bob::dump_as_signed(value);

    // Should not be possible without std:: prefix:
    cout << "foobar" << endl;
}

On the other hand, Bob tried to prevent such scenarios by wrapping the implementation inside a dummy namespace, and making only these symbols available, which are intended for other users:

// This file is written by Bob
#include <iostream>
#include <type_traits>

// Namespace for public use:
namespace bob {

    // Implementation details:
    namespace impl_ {

        // Visible ONLY within sub-namespace:
        using std::cout;
        using std::endl;

        using std::is_integral;
        using std::make_signed;

        // No repeated std:: prefixes at all:
        template <typename T,
            typename S = typename make_signed<T>::type>
        void dump_as_signed(const T i) {
            static_assert(is_integral<T>::value, "no integer");

            // Do something very very useful:
            cout << "signed:" << static_cast<S>(i) << endl;
        }
    }

    // Make available without poisoning with std::*:
    using impl_::dump_as_signed;

}

Because of all using directives are wrapped into a dummy "impl_" namespace within Bobs major namespace, there is no risk for Alice to accidently import symbols from the std:: namespace, too.

So, my questions are:

  1. I don't like that there is a dummy namespace for implementation details, which is "theoretically" visible for everybody. Is there a nicer way, to be able to use many symbols from for e. g. std:: without leaking these AND without prefixing every symbol explicit with std::? (I'm also thinking about generated API-Docs, which show "bob::impl_::XYZ" instead of "bob::XYZ".)
  2. I think, it is not very DRY to repeat std:: a. s. o. again and again everywhere. I also understand that a relatively global "using namespace std" inside a larger scope (such as a class) isn't that beautiful, but hundreds of std:: prefixes are much uglier, in my opinion. Aside from poisoning problems: Which do you think is nicer, and WHY? Or got a completely different idea?

OK, I hope my question is clear. Thanks for reading! :)

回答1:

Let's suppose that Alice is using two libraries, made by Bob and Charlie.

// This file is written by Alice:
#include <bob.hpp>
#include <charlie.hpp>

int main(void) {
  using namespace bob;
  using namespace charlie;

  // Do a bunch of stuff
}

Now, Charlie invents a new feature called foobar which he adds to his library. foobar is great and his users like it. Alice starts using it also.

Then Bob says, "I like foobar also, I want to have my own foobar I can use in my library. But I don't want a dependency on Charlie." So he creates his own version.

Uh oh, now Alice's code doesn't compile! Every usage of foobar in Alice's code is ambiguous and she has to rewrite her whole project.

Then, the same thing happens next month. And the next month after that.

Now, all of Alice's customers are really unhappy because they are building large technologies and trying to keep up-to-date versions of their dependencies, but every time they try to upgrade anything, Alice's code craps out. They make a lot of bug reports on her bug tracker.

Alice sends an email to Bob and Charlie and says

Guys, you have to stop making classes with the same names, or I'm going to lose all of my business!

Bob and Charlie send an email back to Alice:

No Alice, you need to stop putting using namespace bob; and using namespace charlie; in your code. That is not supported by Bob or by Charlie.


Now, let's tell the same story again, except there is no Charlie. It's just Alice making her own classes in her project, colliding with new names added by Bob.


In short, a using namespace directive is never a good idea (in my opinion). Especially when the namespace is an external library. You don't really know how that namespace can change in the future, and if it changes in a way that is at all bad for you, you suddenly have a huge mess on your hands.

Using namespace = to shorten namespaces is often a very good idea. I like to do the following:

namespace my_lib {

namespace qi = boost::spirit::qi;

// Do stuff with qi
// ...

} // end namespace my_lib

That way I get to use the short name qi in my_lib, but I don't impose anything on my users. (Who I expect will not be doing using namespace my_lib;!)

If you are a user, you could do something like

namespace cha = charlie::name::space::is_way_too_long;

But, you should be more than happy to type short namespaces like bob:: or std::, whether you are a user or a library implementor, if it will mean that your code doesn't break when the libraries are upgraded.

This is not about DRY. Putting some kind of qualifier on names makes it much easier to read your code and understand what it means.

For instance look at SDL, a popular C library. To my knowledge, every macro in SDL begins SDL_ and every function begins sdl_. Is that a violation of "DRY"? No. There are no duplicated implementation details here -- the common prefix is there to avoid name collisions. Also, it makes the code more readable and maintainable -- whenever I see a symbol that is talking about an SDL entity I know so immediately. It's very helpful to both the humans and the computers.

Putting using namespace std; or using namespace my_lib; is like taking one of C++ best features and throwing it in the garbage. The trade-off is, save yourself typing 5 characters, at the cost of making a great harm to readability and maintainability.


Parting thought: How does using namespace affect the quality of the error messages that you get.

Here's a simple program that doesn't compile:

#include <iostream>

struct foo {};

int main() {
  std::cout << foo{} << std::endl;
}

When the compiler sees this code, it is going to have to try every stream operator overload that it knows about and check if foo is convertible to any of those things. Because std::cout is one of the arguments, ADL means that we have to search the entire std namespace. Turns out, surprise surprise, foo isn't convertible to any of those things. On gcc 5.3 I get the following (200 line) error message.

main.cpp: In function ‘int main()’:
main.cpp:6:13: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘foo’)
   std::cout << foo{} << std::endl;
             ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:628:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = foo] <near match>
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
     ^
/usr/include/c++/5/ostream:628:5: note:   conversion of argument 1 would be ill-formed:
main.cpp:6:20: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:108:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(__ostream_type& (*__pf)(__ostream_type&))
       ^
/usr/include/c++/5/ostream:108:7: note:   no known conversion for argument 1 from ‘foo’ to ‘std::basic_ostream<char>::__ostream_type& (*)(std::basic_ostream<char>::__ostream_type&) {aka std::basic_ostream<char>& (*)(std::basic_ostream<char>&)}’
/usr/include/c++/5/ostream:117:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ios_type& (*)(std::basic_ostream<_CharT, _Traits>::__ios_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>; std::basic_ostream<_CharT, _Traits>::__ios_type = std::basic_ios<char>]
       operator<<(__ios_type& (*__pf)(__ios_type&))
       ^
/usr/include/c++/5/ostream:117:7: note:   no known conversion for argument 1 from ‘foo’ to ‘std::basic_ostream<char>::__ios_type& (*)(std::basic_ostream<char>::__ios_type&) {aka std::basic_ios<char>& (*)(std::basic_ios<char>&)}’
/usr/include/c++/5/ostream:127:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(ios_base& (*__pf) (ios_base&))
       ^
/usr/include/c++/5/ostream:127:7: note:   no known conversion for argument 1 from ‘foo’ to ‘std::ios_base& (*)(std::ios_base&)’
/usr/include/c++/5/ostream:166:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(long __n)
       ^
/usr/include/c++/5/ostream:166:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long int’
/usr/include/c++/5/ostream:170:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned long __n)
       ^
/usr/include/c++/5/ostream:170:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long unsigned int’
/usr/include/c++/5/ostream:174:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(bool __n)
       ^
/usr/include/c++/5/ostream:174:7: note:   no known conversion for argument 1 from ‘foo’ to ‘bool’
In file included from /usr/include/c++/5/ostream:638:0,
                 from /usr/include/c++/5/iostream:39,
                 from main.cpp:1:
/usr/include/c++/5/bits/ostream.tcc:91:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char; _Traits = std::char_traits<char>]
     basic_ostream<_CharT, _Traits>::
     ^
/usr/include/c++/5/bits/ostream.tcc:91:5: note:   no known conversion for argument 1 from ‘foo’ to ‘short int’
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:181:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned short __n)
       ^
/usr/include/c++/5/ostream:181:7: note:   no known conversion for argument 1 from ‘foo’ to ‘short unsigned int’
In file included from /usr/include/c++/5/ostream:638:0,
                 from /usr/include/c++/5/iostream:39,
                 from main.cpp:1:
/usr/include/c++/5/bits/ostream.tcc:105:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char; _Traits = std::char_traits<char>]
     basic_ostream<_CharT, _Traits>::
     ^
/usr/include/c++/5/bits/ostream.tcc:105:5: note:   no known conversion for argument 1 from ‘foo’ to ‘int’
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:192:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned int __n)
       ^
/usr/include/c++/5/ostream:192:7: note:   no known conversion for argument 1 from ‘foo’ to ‘unsigned int’
/usr/include/c++/5/ostream:201:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(long long __n)
       ^
/usr/include/c++/5/ostream:201:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long long int’
/usr/include/c++/5/ostream:205:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned long long __n)
       ^
/usr/include/c++/5/ostream:205:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long long unsigned int’
/usr/include/c++/5/ostream:220:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(double __f)
       ^
/usr/include/c++/5/ostream:220:7: note:   no known conversion for argument 1 from ‘foo’ to ‘double’
/usr/include/c++/5/ostream:224:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(float __f)
       ^
/usr/include/c++/5/ostream:224:7: note:   no known conversion for argument 1 from ‘foo’ to ‘float’
/usr/include/c++/5/ostream:232:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(long double __f)
       ^
/usr/include/c++/5/ostream:232:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long double’
/usr/include/c++/5/ostream:245:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(const void* __p)
       ^
/usr/include/c++/5/ostream:245:7: note:   no known conversion for argument 1 from ‘foo’ to ‘const void*’
In file included from /usr/include/c++/5/ostream:638:0,
                 from /usr/include/c++/5/iostream:39,
                 from main.cpp:1:
/usr/include/c++/5/bits/ostream.tcc:119:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__streambuf_type*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__streambuf_type = std::basic_streambuf<char>]
     basic_ostream<_CharT, _Traits>::
     ^
/usr/include/c++/5/bits/ostream.tcc:119:5: note:   no known conversion for argument 1 from ‘foo’ to ‘std::basic_ostream<char>::__streambuf_type* {aka std::basic_streambuf<char>*}’
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:574:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const unsigned char*)
     operator<<(basic_ostream<char, _Traits>& __out, const unsigned char* __s)
     ^
/usr/include/c++/5/ostream:574:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const unsigned char*’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:569:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const signed char*)
     operator<<(basic_ostream<char, _Traits>& __out, const signed char* __s)
     ^
/usr/include/c++/5/ostream:569:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const signed char*’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:556:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const char*)
     operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
     ^
/usr/include/c++/5/ostream:556:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const char*’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/ostream:638:0,
                 from /usr/include/c++/5/iostream:39,
                 from main.cpp:1:
/usr/include/c++/5/bits/ostream.tcc:321:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const char*)
     operator<<(basic_ostream<_CharT, _Traits>& __out, const char* __s)
     ^
/usr/include/c++/5/bits/ostream.tcc:321:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const char*’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:539:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const _CharT*)
     operator<<(basic_ostream<_CharT, _Traits>& __out, const _CharT* __s)
     ^
/usr/include/c++/5/ostream:539:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   mismatched types ‘const _CharT*’ and ‘foo’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:519:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, unsigned char)
     operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
     ^
/usr/include/c++/5/ostream:519:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘unsigned char’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:514:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, signed char)
     operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
     ^
/usr/include/c++/5/ostream:514:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘signed char’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:508:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, char)
     operator<<(basic_ostream<char, _Traits>& __out, char __c)
     ^
/usr/include/c++/5/ostream:508:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘char’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:502:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, char)
     operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
     ^
/usr/include/c++/5/ostream:502:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘char’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/iostream:39:0,
                 from main.cpp:1:
/usr/include/c++/5/ostream:497:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, _CharT)
     operator<<(basic_ostream<_CharT, _Traits>& __out, _CharT __c)
     ^
/usr/include/c++/5/ostream:497:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   deduced conflicting types for parameter ‘_CharT’ (‘char’ and ‘foo’)
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/bits/ios_base.h:46:0,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from main.cpp:1:
/usr/include/c++/5/system_error:209:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::error_code&)
     operator<<(basic_ostream<_CharT, _Traits>& __os, const error_code& __e)
     ^
/usr/include/c++/5/system_error:209:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const std::error_code&’
   std::cout << foo{} << std::endl;
                    ^
In file included from /usr/include/c++/5/string:52:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from main.cpp:1:
/usr/include/c++/5/bits/basic_string.h:5172:5: note: candidate: template<class _CharT, class _Traits, class _Alloc> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&)
     operator<<(basic_ostream<_CharT, _Traits>& __os,
     ^
/usr/include/c++/5/bits/basic_string.h:5172:5: note:   template argument deduction/substitution failed:
main.cpp:6:20: note:   ‘foo’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
   std::cout << foo{} << std::endl;
                    ^

Here's the point: If you do using namespace bob;, then every one of bob's types that is streamable is also going to appear in that list! If you do using namespace charlie; then all of his types will be there too!

Not only will the error messages be worse, there's a greater chance that you can get some really bizarre interaction that you didn't expect. What if Bob's types are occasionally streamable into one of Charlie's types? And Charlie's types are occasionally implicitly convertible to some standard type that is streamable?

And of course this all applies not just to any operator overload, but any template or function call.

So, bottom line, C++ is a lot easier to reason about and works a lot better if you avoid mixing lots of crap together in one namespace.