0

So I'm writing operator<< overload for a class as following:

#test.hh
#pragma once
#include<iostream>
namespace ns {
    struct test {
        double d;
        test(double x): d(x) {}
        friend std::ostream& operator<<(std::ostream&, const test&);
    };
}
#test.cc
#include"./test.hh"
std::ostream& operator<<(std::ostream& os, const ns::test& a) {
    os << a.d;
    return os;
}

Testing with following main:

#main.cc
#include"./test.hh"
#include<iostream>
int main() {
    ns::test a(12);
    std::cout << a << std::endl;
    return 0;
}

Which when compiled with g++ test.cc main.cc gave back error:

/usr/sbin/ld: /tmp/cc6Rs93V.o: in function `main':                                                      
main.cc:(.text+0x41): undefined reference to `ns::operator<<(std::ostream&, ns::test const&)'           
collect2: error: ld returned 1 exit status

Apparently the compiler resolves the function to ns::operator<< when it should be calling operator<<. I understand that C++ will find function within the namespace of argument, but did I implement the operator overloading incorrectly? Answers like this seems to have same implementation like me, which the only difference is they all wrote them in header.

Which part did I do wrong? And how could I fix such issue?

Andrew.Wolphoe
  • 420
  • 5
  • 18
  • Did you try putting the implementation in the same namespace as the declaration? (wrapping it in `namespace ns { }`) – Joachim Isaksson Sep 18 '21 at 09:07
  • @JoachimIsaksson Adding namespace wrapping did solve it, but why? I mean if I wrote `std::ostream& ns::operator<<` in `test.cc`, it gave me `has not been decla|~ red within ‘ns’ ... note: only here as a ‘friend’`. And also operators like such (`operator<<`, `operator+`), shouldn't those be defined in global scope? As we're just overloading it with different parameters (here `ns::test` as second parameter). – Andrew.Wolphoe Sep 18 '21 at 09:10
  • @Andrew.Wolphoe friend declarations are weird. They're only visible to some kinds of name lookup, but not others. – Sebastian Redl Sep 18 '21 at 09:15
  • 1
    `friend std::ostream& operator<<(std::ostream&, const test&);` declares `operator<<` in the namespace where the declaration is found. If you want to declare a global `operator<<`, you can do it: `friend std::ostream& ::operator<<(std::ostream&, const test&);` but only if a declaration of `::operator<<` is already visible at that point. I do not recommend it as it pollutes the global namespace. Declare and define `operator<<` in `namespace ns`, that's how namespaces are supposed to be used. – n. m. could be an AI Sep 18 '21 at 11:12

2 Answers2

0

This is my solution for cases like this:

#include<iostream>

namespace ns 
{
    struct test 
    {
        double d;
        test(double x) : d(x) {}
        friend std::ostream& operator<<(std::ostream&, const test&);
    };

    std::ostream& operator<<(std::ostream& os, const ns::test& a) {
        os << a.d;
        return os;
    }
}

int main() 
{
    ns::test a(12);
    std::cout << a << std::endl;
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • Separating implementation from header is recommended (at least what I'm taught and currently preferred). It really have some impact when building large projects with autotools, which can save you plenty of compilation time – Andrew.Wolphoe Sep 18 '21 at 09:13
  • You understood correctly! :) Its just that stack overflow doesn't do seperate files very well. (I probably should have separated them out, just wanted to show the minimum amount of code) If you really want to understand more about separation of header files from code look at abstract base classes (and factories), dependency injection & the pimpl pattern :) Happy coding – Pepijn Kramer Sep 18 '21 at 09:21
0

The compiler uses the argument dependent lookup (ADL). As the second operand of the operator has the type specifier test declared in the name space ns then the compiler searches the operator also in the class definition of test and in the namespace ns where the class is declared. And such an operator is found because its declaration is present in the scope of the class test but the operator is not defined. So the compiler issues an error.

If you will define the operator then the compiler will issue another error that there are defined two operator and an ambiguity takes a place.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335