Jack Poulson Jack Poulson - 2 months ago 7
C++ Question

Importing a single class into a namespace appears to require that there be no function name collisions between the namespaces

The following code (surprisingly?) does not compile with either

g++
or
clang++
due to ambiguous calls to
BuildStream
when instantiating
bar::BuildStream
with
bar::Rectangle
as an input.

#include <iostream>
#include <sstream>

namespace foo {

struct Rectangle { double height, width; };
std::ostream& operator<<(std::ostream& os, const Rectangle& a) { return os; }

inline void BuildStream(std::ostringstream& os) { }

template<typename T, typename... Args>
void BuildStream
(std::ostringstream& os, const T& item, const Args& ...args) {
os << item;
BuildStream(os, args...);
}

} // namespace foo

namespace bar {

inline void BuildStream(std::ostringstream& os) { }

template<typename T, typename... Args>
void BuildStream
(std::ostringstream& os, const T& item, const Args& ...args) {
os << item;
BuildStream(os, args...);
}

using Rectangle = foo::Rectangle;

} // namespace bar

int main(int argc, char* argv[]) {
std::ostringstream os;
bar::BuildStream(os, 1, 2);
bar::BuildStream(os, bar::Rectangle(), bar::Rectangle());
return 0;
}


The ambiguity would seem to imply that, generally speaking, if a class is imported from namespace
foo
into namespace
bar
, then care must be taken to ensure that no functions in
bar
have names equal to functions in
foo
. This clearly throws a wrench in some of the benefits of namespaces; is there a more nuanced approach to avoiding such ambiguities?

The context is
foo
being the namespace of a large library and
bar
being a consumer project's namespace (and this problem showed up in practice).

Answer

The issue here is with argument-dependent lookup. bar::Rectangle is actually a foo::Rectangle, so the call to BuildStream(os,args...) needs to check in foo's namespace as well for overloads.

Care does need to be taken. You can avoid the ambiguity by qualifying the call:

  template<typename T, typename... Args>
  void BuildStream(std::ostringstream& os, const T& item, const Args& ...args)
  {
    os << item;
    bar::BuildStream(os, args...); // qualified
  }