0

[solved] I had some bragging here that was inaccurate, the problem is related to compilation.

My code is:

#include <iostream>
using namespace std;

struct Circle {
    void write(ostream& os) const {
        os<<*this;
    }
};

ostream& operator<<(ostream& os, const Circle& rhs_c) {
    os<<"circle";
    return os;
}

int main() {
    Circle c1;
    c1.write(cout);
    return 0;
}

I get the following error:

C2679 binary'<<': no operator found which takes a right-hand operand of type 'Circle' (or there is no acceptible conversion)

Whereas if the global operator<< actually has something that doesn't match the operands I get:

no operator "<<" matches these operands

I have reasons for having such a twisted print function that may break knowing why the upper code is wrong.

Kabir
  • 85
  • 6
  • C++ works from the top down. At the place where you try to call your operator it hasn't been declared yet, so as far as the compiler knows it doesn't exist at all. You need to either add a prototype before the definition of the function that uses it, or move the use after it. Your code also won't compile because you do not return a value from your operator, and the write function is private so you can't actually call it the way you're trying. https://ideone.com/ZEpQ5b – Retired Ninja Apr 27 '18 at 22:04
  • Thanks for the observations, I edited the question regarding the return and access bugs. – Kabir Apr 27 '18 at 22:33
  • Declare the `operator<<()` before (i.e. above in the file) calling it in `write()`. It can still be defined (implemented) after the definition of `write()` though. – Peter Apr 27 '18 at 22:55

2 Answers2

2

When you do:

os<<*this;

the operator overloading hasn't happened yet, and to exact, the compiler is not informed that it will happen later in that code.

Forward declare a prototype of the operator's overloading, which says to the compiler that you, the programmer, promises to provide a definition for this overloading.

Then the compiler will know what to do when it meets this line (os<<*this;).

But it wouldn't know what Circle is, right? So you need to also forward declare the class' declaration too.


Moreover, you probably forgot to return your stream in the operator's overloading.


Putting everything together, and using public scope for simplicity (but please read about friend scope for operator overloading here), you get:

#include <iostream>
using namespace std;

class Circle;
ostream& operator<<(ostream& os, const Circle& rhs_c);

class Circle {
    public:
    void write(ostream& os) const {
        os<<*this;
    }
};

ostream& operator<<(ostream& os, const Circle& rhs_c) {
    // param 'rhs_c' should be used of course
    os<<"circle";
    return os;
}

int main() {
    Circle c1;
    c1.write(cout);
    return 0;
}
gsamaras
  • 71,951
  • 46
  • 188
  • 305
2

The problem is that you are trying to call an operator that "doesn't exist yet." Because you defined your write() function in the header, and declared the operator<< below the class, at the point of invocation, as far as the compiler is concerned, operator<< is not included in the overload set.

To include it in the overload set, you must declare the function first, before your class. But to declare the function it has to know about the class, so you must also declare that. It is messy-ish and sounds worse than it is:

// declare that Circle is a class, so the compiler will know what a reference to it means
class Circle;

// declare the operator, but not define it yet, since Circle is incomplete.    
ostream& operator<<(ostream& os, const Circle& rhs_c);

class Circle {
    void write(ostream& os) const {
        os<<*this;
    }
};

inline ostream& operator<<(ostream& os, const Circle& rhs_c) {
    os<<"circle";
}

This way, when the operator is called in write(), the compiler knows the operator exists, and then later it is defined and the linker will make the call happen.

There is another way too, and that is to NOT implement write() in the class definition, but after the operator<< is declared. Perhaps move the definition to a .cpp file, but you could also implement it outside the class for the same effect:

class Circle {
    void write(ostream& os) const;
};

inline ostream& operator<<(ostream& os, const Circle& rhs_c) {
    os <<"circle";
}

inline void Circle::write(ostream& os) const {
    return os << *this;
}

Both ways the same thing is at play here: the definition of write() is physically located after the declaration of the operator it expects to use. (If placed in the .cpp file, remove inline.)

Personally I recommend moving the implementation to a .cpp file. Not only do you avoid these kinds of problems, you can also avoid including in your header, and it's a big file (tens of thousands of lines of code to compile anytime your header is included.) Instead, you should only use "forward" declarations to iostream classes available from the header, and then you can get away with this pattern:

// header file
#include <iosfwd>
#pragma once

class Circle {
   void write(std::ostream&) const;
};

std::ostream& operator<<(std::ostream& os, Circle const& circle);

And then

// .cpp file
#include "Circle.hpp"
#include <iostream>

void Circle::write(std::ostream& os) {
   os << *this;
}

std::ostream& operator<<(std::ostream& os, Circle const& circle) {
   return os << "circle";
}

Splitting it this way, you include the much smaller class in your header, and the heave only once in your .cpp file. It also means that all the functions in the .cpp file see all the declarations in the header (so ordering no longer matters). Then your header is cleaner, easier to read class interface, while the .cpp file contains the heavier implementation detail that is only compiled once (instead of everywhere your header is included.)

Chris Uzdavinis
  • 6,022
  • 9
  • 16