2

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

Jack Poulson
  • 1,305
  • 1
  • 10
  • 13
  • You will get the same error if you: 1. Remove the "using", 2. Pass "foo::Rectangle" from main. This is due to [argument-dependent lookup](http://stackoverflow.com/questions/8111677/what-is-argument-dependent-lookup-aka-adl-or-koenig-lookup). Also explicitly invoking bar::BuildStream() from the template function will fix the compilation error. – Sam Varshavchik Sep 18 '16 at 17:23
  • Thank you for the link to argument-dependent lookup; that is the right nuanced discussion for explaining this (somewhat unfortunate) behavior. – Jack Poulson Sep 18 '16 at 17:44

1 Answers1

2

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
  }
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132