Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't a function in a namespace see my operator<< defined globally?

I've defined an operator<< output function for std::pair instances, for use by some unit tests that want to print the values if they don't watch what's expected. My test code also has pairs that are held as members of another class that has its own operator<< — specifically boost::optional, but for the sake of example I've defined a simple Container class here instead. The problem is that the operator<< for std::pair values doesn't seem to be visible within the operator<< of the container class.

#include <iostream>
#include <utility>

template <typename T1, typename T2>
std::ostream &operator<<(std::ostream &out, std::pair<T1, T2> const &pair) {
  return out << "{ " << pair.first << ", " << pair.second << " }";
}

namespace {

  template <typename T>
  struct Container {
    T value;
  };

  template <typename T>
  std::ostream &operator<<(std::ostream &out, Container<T> const &container) {
    return out << container.value;  // Error!
  }

}

int main() {
  std::pair<char, int> pair { 'a', 1 };
  Container<std::pair<char, int>> container { pair };

  std::cout << pair << std::endl;
  std::cout << container << std::endl;
}

The line near the end that outputs the plain pair works fine. But when trying to output the pair within the container, the compiler can't find the operator<< for pairs. Here's the message from GCC:

test.cc: In instantiation of ‘std::ostream& {anonymous}::operator<<(std::ostream&, const {anonymous}::Container<T>&) [with T = std::pair<char, int>; std::ostream = std::basic_ostream<char>]’:
test.cc:28:16:   required from here
test.cc:18:16: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘const std::pair<char, int>’)
     return out << container.value;
            ~~~~^~~~~~~~~~~~~~~~~~

…followed by a long listing of all the candidate operator<< functions that were considered, and why each one isn't suitable (because they're all for different types of values). My template for std::pair is not in the list.

(This message is from Debian's GCC 6.3.0 with -std=c++14. I get the same error, with different wording, from Debian's Clang 3.8.1-24 with -std=c++14, and Apple's Clang 1000.11.45.5 (Apple LLVM 10.0.0) with -std=c++17.)

If I remove the anonymous namespace around my Container template and its operator<<, the error goes away. But that's not really a solution, since in reality the container is boost::optional, which of course is in namespace boost, and I can't change that.

It's not clear to me why my global operator<< isn't visible from within the namespace, since global scope should be part of the search path for unqualified lookup. My best guess is that it's because my operator<< is a template, and templates don't seem to be part of the initial unqualified lookup, so ADL kicks in and finds a bunch of other operator<< functions defined in std:: and as members within std::ostream, so the lookup stops there. The list of candidate functions (in the compiler's error message) seems to be consistent with that interpretation. But then it's unclear why it does work when the container is not in a namespace.

Is there a way to make this work without modifying the Container class?


(As background: I'm using the Boost.Test library and writing lines like BOOST_TEST(some_func() == boost::make_optional(std::make_pair('a', 1))), where BOOST_TEST does some macro/template magic to extract the two sides of the expression and output their values if they don't match. That requires the values to have an operator<< defined. Boost provides one for optional, and I've written one for the std::pair within it, but the call from the former to the latter is where the problem lies.)

like image 443
Wyzard Avatar asked Feb 22 '19 05:02

Wyzard


2 Answers

Unqualified lookup goes up one level at a time and stops as soon as it finds something. It finds an operator<< within the anonymous namespace - the very one you are calling from - and stops dead right there.

Consider wrapping an element of the pair or the pair itself into a wrapper in your own namespace. Then you can define an operator<< to do whatever you want and have it picked up by ADL.

like image 63
T.C. Avatar answered Sep 27 '22 23:09

T.C.


Is there a way to make this work without modifying the Container class?

Yes. You have to put the operator<< inside the namespace.

DEMO here.

Search for operator << is only happening within the namespace container.value is defined in. Related Post.

like image 37
P.W Avatar answered Sep 27 '22 23:09

P.W