2

I encountered a strange error regarding name lookup in C++.

The error can be recreated using the following minimal example:

#include <vector>
#include <iostream>

std::ostream& operator<<(std::ostream& out, const std::vector<int>& a) {
    for (size_t i = 0; i < a.size(); i++) {
        out << a[i] << std::endl;
    }
    return out;
}

namespace Test {

    struct A {
        // Label 1
        friend std::ostream& operator<<(std::ostream& out, const A&) {
            return out << "A" << std::endl;
        }
    };

    struct B {
        void printVector() noexcept {
            std::vector<int> v{1, 2, 3};
            std::cout << v << std::endl; // The error occurs in this line
        }
        // Label 2
        friend std::ostream& operator<<(std::ostream& out, const B&) {
            return out << "B" << std::endl;
        }
    };

}

int main() {
    Test::B().printVector();
}

Compiling this will result in the following error message:

cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'

You can test this for yourself here: http://cpp.sh/5oya

The strange part is, that the code compiles and runs fine if you remove either one of the functions labeled with // Label 1 respectively // Label 2.

My question now is: What is going on here? How can it be fixed?

zuenni
  • 23
  • 3
  • [Works here](http://coliru.stacked-crooked.com/a/99865250debbfc32) – NathanOliver Jan 12 '17 at 19:31
  • Visual Studio 2015 produces this error message : "error C2679: binary '<<': no operator found which takes a right-hand operand of type 'std::vector>' (or there is no acceptable conversion)" – François Andrieux Jan 12 '17 at 19:33
  • g++ version 5.3.1 produces this error message : `Error: no match for »operator<<« (operand types are »std::ostream {aka std::basic_ostream}« and »std::vector«)` – zuenni Jan 12 '17 at 19:42
  • Possible duplicate of [Namespaces and operator resolution](http://stackoverflow.com/questions/5195512/namespaces-and-operator-resolution) – Rama Jan 12 '17 at 20:19
  • Cannot reproduce with MinGW g++ 6.3.0, but Visual C++ 2015 has sort of the same error. Like the online g++ 4.9.2 you used it considers only `basic_ostream::operator<<` and `std::operator<<`. Which is weird. Weirder: it does find the overload if it's placed in namespace `Test`, i.e. it *does* look in outer namespaces, not just class and ADL. And this happens also with the online g++ 4.9.2. – Cheers and hth. - Alf Jan 12 '17 at 20:26

2 Answers2

0

You are having ADL troubles, though I think name lookup should have found your overload in the global namespace. (I don't have specific C++ standard quotes to know if the compiler is wrong yet). GCC (version 6.3) and Clang (version 3.8.0) compilers finds the overload as seen here

One work around is to import the name of the global operator into your current namespace:

std::vector<int> v{1, 2, 3};
using ::operator <<; 
std::cout << v << std::endl;

As seen here: http://cpp.sh/95kuq

Another work around will be to explicitly call the global overload like:

std::vector<int> v{1, 2, 3};
::operator << (std::cout,  v); 
std::cout << std::endl;

As seen here: http://cpp.sh/4btyq

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • Yes, this works, but it removes the beauty of using overloaded operators. – zuenni Jan 12 '17 at 19:44
  • @zuenni, well, you could simply import the global operator like `using ::operator<<;` as seen [here](http://cpp.sh/95kuq). I've revised my answer to reflect that – WhiZTiM Jan 12 '17 at 23:13
  • Thanks for your solutions. Both indeed do work. But I prefer a solution, where the calling code can be kept unchanged and looks like one would expect it to look. – zuenni Jan 13 '17 at 09:21
0

Other workaround will be to overload operator << inside the namespace std, instead of in the global namespace.(The lookup will find it at namespace scope)

namespace std
{
    std::ostream& operator<<(std::ostream& out, const std::vector<int>& a) {
        for (size_t i = 0; i < a.size(); i++) {
            out << a[i] << std::endl;
        }
        return out;
    }
}

Try Here

[EDITED]

Another workaround, and for puritan people that don't want to pollute global namespace, or the std, is to

...have the insertion operators in the same namespace as the class upon which it operates.

...as discussed here.

namespace Test
{
    std::ostream& operator<<(std::ostream& out, const std::vector<int>& a) {
        for (size_t i = 0; i < a.size(); i++) {
            out << a[i] << std::endl;
        }
        return out;
    }
}

Working code here.

Community
  • 1
  • 1
Rama
  • 3,222
  • 2
  • 11
  • 26
  • I don't like that this requires to extend the std namespace. But using this work around does not require to change the calling code, so I will use this solution. Thanks a lot. – zuenni Jan 13 '17 at 09:17
  • @Rama, This solution actually invokes Undefined Behavior, hence it *"doesn't work**. You may specialize any template in the `std::` namespace, but you are not permitted to overload any function in `std::` nemespace. The ISO C++ standard [invokes Undefined behavior to any C++ program that adds anything to](http://eel.is/c++draft/namespace.constraints#namespace.std-1) the `std::` namespace. – WhiZTiM Jan 13 '17 at 10:50
  • @WhiZTiM Ohh!! so you think that doing naked at the global scope it is more scoped than in a particular namespace? – Rama Jan 13 '17 at 11:40