Link to home
Start Free TrialLog in
Avatar of shadow66
shadow66

asked on

Overloading << in BCB4.0

Compiling the following console app using Borland C++ Builder 4.0 results in an error:

#include <condefs>
#include <fstream>
#pragma hdrstop

class A {
    public:
        A(int val) : value(val) {}
        int getValue() {return value;}
    private:
        int value;
};

std::ofstream& operator<<(std::ofstream& os, A& a) {
    os << a.getValue();
    return os;
}

int main()
{
    int i(1);
    A a(2);

    std::ofstream outFile("test.txt");
    outFile << i << '\n';
    outFile << a << '\n';
    outFile << int(3) << '\n';
    outFile << A(4) << '\n';              // error (see text below)
    outFile.close();

    return 0;
}

[C++ Error] experiment.cpp(27): E2094 'operator<<' not implemented in type 'std::ofstream' for arguments of type 'A'.

When I comment this line out, the program compiles, runs and writes:
1
2
3

to test.text as desired. Why can't I instantiate an A in mid-stream as I can with int?

Thanks.
ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of nietod
nietod

when you create an unnamed temporary like A(4) the temporary is an R value, that is, it can be used in circumstances where it is treated like a constant.  But not in circumstances where it woudl be changed.  This is because code that changes it is probably an error, because as a temporary, it is going to be quickly destroyed so the changes would be lost.   The overloaded << operator takes an A a non-constant reference.  As far as the compiler "knows", this means the operator << function will try to change the A object that is passed to it.  But this is not allowed since the A in this case is a temporary.  

continues
To make this work, you can do 1 of two things.  One solution, probably not a good one is to make the overloaded << operator take the A by value instead of by reference, like

std::ofstream& operator<<(std::ofstream& os, A a)

This will work because it copies the temporary A to a local A so the compiler knows the procedure won't be able to change the temporary A.

But that is ineffiicent because the copy constructor has to be called and can be slow, especially for large or complex classes.  So a better solution is to pass the A operand by constant reference.  Like

std::ofstream& operator<<(std::ofstream& os, const A& a)

In this case a copy does not have to be made because the object is passed by reference.  But the compiler knows the procedure won't alter the temporary A object, because it is constant, so it allows the temporary A to be passed.

New if you do this is will produce another problem.  Actually expose an existing one.  The

     int getValue() {return value;}

procedure cannot be called on a cosntant A object because it is not a constant member procedure.  However there is no good reason for this.  it should be made constant, like

     int getValue() const {return value;}

let me know if you have any questions.

Avatar of shadow66

ASKER

Nietod--thanks for posting the R value "hint" first--it gave me time to experiment. I had just completed trying what you later suggested--making getValue() constant and making the A& parameter constant:

  int getValue() const {return value;}

  fstream& operator<<(std::ofstream& os, const A& a) {
  ...
  }

Surprisingly, these two changes resulted the following error:  
[C++ Error] experiment.cpp(14): E2015 Ambiguity between 'std::basic_ostream<char,std::char_traits<char> >::operator <<(int)' and 'operator <<(std::basic_ofstream<char,std::char_traits<char> > &,const A &)'.

It's as if my overload function is too similar to some fstream member function for arguments of type int.

Here's some additional/related behavior. This actually compiles:
#include <condefs>
#include <fstream>
#include <string>
#pragma hdrstop

class A {
    public:
        A(std::string val) : value(val){}
        std::string getValue() const {return value;}
    private:
        std::string value;
};

std::ofstream& operator<<(std::ofstream& os, const A& a) {
    os << a.getValue();
    return os;
}

int main()
{
    std::ofstream outFile("test.txt");
    outFile << A("abc") << '\n';
    outFile.close();

    return 0;
}

but when run crashes w/ an EStackOverflow exception.  

Get this: It even crashes if you rewrite main() as:

int main()
{
    std::string s("abc");

    std::ofstream outFile("test.txt");
    outFile << s << '\n';
    outFile.close();

    return 0;
}

but if you remove the class definition and overloaded function from the program, it runs fine! Why would the existence of class A and associated operator<< function affect the output of a std::string to a file?
>> Surprisingly, these two changes resulted the following error:  
Which is the line that produces this.

It sounds like it needs to perform a conversion on this line and it doesn't know which conversion to use.  The two choices it finds is to convert the value to int and call the int overload (one provided for you already) or it can use your A::A(int) constructor to convert the value to an A object and call the A overload that you wrote.  The fix is usually to use a little casting to hekp it decide which way to go, but I'd like to know more about the line with the problem first.
>> Why would the existence of class A and
>> associated operator<< function affect the
>> output of a std::string to a file?

I assume what is happening is that in

   outFile << s << '\n';

it can't print s directly soo it tries to convert s.  it uses the A constructor ("conversion constructor") to create an A object from s and then calls the overload you wrote for an A object.

However, I can't explain why this converison takes place.  I thought a string would output as is, whith no need to convert.  Furthermore, I can't explain the crash.  What compiler are you using?  What version?
>>Which is the line that produces this.

Oops, ya that would be helpful. From the original program in which A's data member was of type int:

std::ofstream& operator<<(std::ofstream& os, const A& a) {
    os << a.getValue();  // This line failed (error listed below)
    return os;
}

Error: [C++ Error] experiment.cpp(15): E2015 Ambiguity between 'std::basic_ostream<char,std::char_traits<char> >::operator <<(int)' and 'operator <<(std::basic_ofstream<char,std::char_traits<char> > &,const A &)'.

You would think a.getValue() would be of type int and therefore the ofstream member function operator<<(int&) would be used. I don't get why it would even consider my operator<<(ofstream&, const A&) function for this statement, since getValue() returns an object of type int, not of type A.

>>I assume what is happening is that in
     outFile << s << '\n';
     it can't print s directly soo it tries to convert s.  

But, in the second program, if I completely remove the declaration of A and the declaration of the overloaded operator>>, then s prints just fine.

>>What compiler are you using?  What version?

Borland C++ Builder Standard, Version 4.0 Build 14.4

Thanks.
Interesting.  I think this is due to a mistake in the BCB compiler or its STL library.  The code works fine in VC and it worked fine in BCB for me, until it realized one small difference in you code that i didn't have.  When I put in that change VC was fine but BCB wasn't.

std::ofstream& operator<<(std::ofstream& os, const A& a)

should work with any type if output stream, not just ofstream, so it should be declared like

std::ostream& operator<<(std::ostream& os, const A& a)

That took care of the problem.  What was happening was that you were getting conversion because inside the procedure it had

  os << a.getValue();

and to perform that it took the string returned by getValue() and converted it to an "A" object using the A(const std::string &) conversion constructor. Then to output the A object it created, it called itself recusively.

Why?  I'm not sure, it may be that the BCB STL doesn't define << for a ofstream and a std::string so it choose to convert the string to an A object to make things work rather than convert the ofstream to an ostream.  However, I believe that of the two conversions the 2nd is the right one (the standard does not leave this up the compiler, there are specific orders in which these conversions are to be performed.)
Great! The following combination of int and string data members works exactly as I wanted:

#include <condefs>
#include <fstream>
#include <string>
#pragma hdrstop

class A {
    public:
        A(int intVal, std::string stringVal) : intValue(intVal), stringValue(stringVal) {;}
        int getIntValue() const {return intValue;}
        std::string getStringValue() const {return stringValue;}
    private:
        int intValue;
        std::string stringValue;
};

std::ostream& operator<<(std::ostream& os, const A& a) {
    os << a.getIntValue() << a.getStringValue();
    return os;
}

int main()
{

    int i(1);
    std::string s("a");
    A a(2,"b");
    std::ofstream outFile("test.txt");
    outFile << i << s << '\n';
    outFile << a << '\n';
    outFile << int(3) << std::string("c") << '\n';
    outFile << A(4,"d") << '\n';
    outFile.close();

    return 0;
}

I agree that it's odd that I couldn't use the exact  class type in my overloaded operator<< function, especially since ofstream inherts ostream. Did some digging and, sure enough, in the <ostream> header file all the member operator<< functions are declared private!

Thanks for your time and efforts. I appreciate you grabbing my question so quickly and sticking with it.