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).
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
}