Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ operator lookup rules / Koenig lookup

While writing a test suite, I needed to provide an implementation of operator<<(std::ostream&... for Boost unit test to use.

This worked:

namespace theseus { namespace core {
    std::ostream& operator<<(std::ostream& ss, const PixelRGB& p) {
        return (ss << "PixelRGB(" << (int)p.r << "," << (int)p.g << "," << (int)p.b << ")");
    }
}}

This didn't:

std::ostream& operator<<(std::ostream& ss, const theseus::core::PixelRGB& p) {
    return (ss << "PixelRGB(" << (int)p.r << "," << (int)p.g << "," << (int)p.b << ")");
}

Apparently, the second wasn't included in the candidate matches when g++ tried to resolve the use of the operator. Why (what rule causes this)?

The code calling operator<< is deep within the Boost unit test framework, but here's the test code:

BOOST_AUTO_TEST_SUITE(core_image)

BOOST_AUTO_TEST_CASE(test_output) {
    using namespace theseus::core;
    BOOST_TEST_MESSAGE(PixelRGB(5,5,5)); // only compiles with operator<< definition inside theseus::core
    std::cout << PixelRGB(5,5,5) << "\n"; // works with either definition
    BOOST_CHECK(true); // prevent no-assertion error
}

BOOST_AUTO_TEST_SUITE_END()

For reference, I'm using g++ 4.4 (though for the moment I'm assuming this behaviour is standards-conformant).

like image 934
John Bartholomew Avatar asked Jan 05 '11 12:01

John Bartholomew


2 Answers

In argument dependent lookup (the correct name for koenig lookup) the compiler adds to the overloaded function set the functions which are declared in the namespaces of each parameter.

In your case, the first operator<< is declared in the namespace thesus::core, which is the type of the argument you call the operator with. Therefore this operator<< is considered for ADL because it's declared in an associated namespace

In the second case, the operator<< seems to be declared in the global namespace which is not an associated namespace as parameter one is of type from namespace std and param 2 is of type from namespace theseus::core.

Actually, probably your 2nd operator<< isn't declared in global namespace as that would be found through looking in parent scopes.. maybe you've got something more like this? If you can post more code we can give a better answer.


Ok I remembered, ADL doesn't lookup in parent scopes when it finds a name in the current scope. So the boost macro BOOST_TEST_MESSAGE expands to include an operator<< and there is some in the scope tree a non-viable operator<< between the expression and global scope. I updated code to illustrate this (hopefully).

#include <iostream>

namespace NS1
{
  class A
  {};

  // this is found by expr in NS2 because of ADL
  std::ostream & operator<<(std::ostream &, NS1::A &);
}


// this is not seen because lookup for the expression in NS2::foo stops when it finds the operator<< in NS2
std::ostream & operator<<(std::ostream &, NS1::A &);

namespace NS2
{
    class B
    {};

    // if you comment this out lookup will look in the parent scope
    std::ostream & operator<<(std::ostream &, B &);

    void foo(NS1::A &a)
    {
        std::cout << a;
    }  
}
like image 117
dancl Avatar answered Nov 08 '22 20:11

dancl


Operator overloading is like a function but differs, and one of the differences is namespace lookup.

Like functions, operator overloads belong in a namespace, but scoping the way you scope a function would be impractical. Imagine if your code had to call

std::cout thesus::core::<< p; // ouch and obviously incorrect syntax

Therefore the << operator must be in the namespace of one of the parameters, either std (for the cout) or the namespace of the p, in this case thesus::core.

This is the Koenig Lookup principle. You must define the operator overload in the correct namespace.

like image 30
CashCow Avatar answered Nov 08 '22 20:11

CashCow