-1

I wanted to print some properties directly to cout. Luckily I found a solution for static calls like this:

// somewhere:
ostream & Foo(ostream &ostr){
    ostr << "Foo";
    return ostr;
}

and another static call where it can be called directly like that:

// somoewhere else:
void doSomething(){
    cout << 123 << Foo << endl;
}

This works fine and prints simply "123Foo". But I want to use this for an object I have, so I could print its properties. In my class A I have a property b of the class B. There the function is implemented similar to the static one before:

ostream & Foo(ostream &ostr) {
    ostr << "my foo is 123";
    return ostr;
}

but it doesn't compile. I also tried to specify the namespace like

ostream & B::Foo(ostream &ostr) { ... }

and assumed I could just call the object's function like this:

cout << 123 << b->Foo << endl;

but it always tells me there is an error: "Reference to non-static member function must be called".

I just can't get this done. What am I doing wrong? Is this even possible?

NOTE: I don't want to overload the << operator or anything. I don't want a "toString()" method or anything similar but would like to understand why this instance method can not be called like anywhere else. As a Java developer I would have guessed something like "My object " + b.foo() would be possible. But it seems, it is not.

Tobias Reich
  • 4,952
  • 3
  • 47
  • 90
  • 4
    Usually you want to implement this as a non-member function like `ostream& operator<<(ostream& o, MyClass& c) { ... }` If you need access to protected/private data you can make it a friend. – Retired Ninja May 14 '23 at 20:40
  • 1
    ostream& operator<<(ostream& o, B& c) gives the error: "Overloaded 'operator<<' must be a binary operator (has 3 parameters)" And when removing the second parameter, it doesn't work either. And after all, wouldn't that allow only 1 function per class? Like a "toString"? I wanted multiple functions like b->foo << " ..." << b->bar << endl; – Tobias Reich May 14 '23 at 20:44
  • Closed as "What are the rules for operator overloading"? Because the question *mentions* the existence of an operator in C++? Talk about reaching for a duplicate..... – Silvio Mayolo May 14 '23 at 20:47
  • 2
    @SilvioMayolo See OP's comment. OP could not properly declare the overloaded `operator<<`, then he seemed to start XY-problem solving. – 273K May 14 '23 at 20:49
  • I don't get it. It's not a question for operator overloading. I need to know why this can't be called since I assume it could be used like in all other places, too where I can simply call b-> doSomething(); – Tobias Reich May 14 '23 at 20:49
  • 2
    Here's an example of what I think you want: https://godbolt.org/z/xdbYMoK8K – Retired Ninja May 14 '23 at 20:53
  • Thanks but that is still only overloading the << operator, right? If I would do that I could only have one function for printing my B. But that doesnt work for multiple functions like cout << b.printA << b.printB << endl; And furthermore, I would love to understand why this isn't possible. What is the difference to normal function calls of an instance? – Tobias Reich May 14 '23 at 20:56
  • 1
    If you want to have different functions to print an object then you do want `ToString()` like functions. `std::string printA(const A& a) { /* return a string formatted how you want */ }` and `std::cout << a.printA();` – Retired Ninja May 14 '23 at 21:00
  • 2
    It seems to me that any mystery here is why `cout << Foo;` works. A function name without () is a function pointer and what sense would there be to have an operator<< overload that takes some random fcn ptr of some type? Why would there be such an overload & what would the << overload do with it? In the general case of SomeRandomFunction, it won't work, but [there are overloads for specific signatures](https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt) _intended "to implement output I/O manipulators such as std::endl"_ – Avi Berger May 14 '23 at 22:05
  • 2
    . . . So you can use fcn pointers for fcns with those specific signature and they will be called. But only those signature - none of which are non-static member fcn pointers which can't work the same way as they can't be called without also providing an object to call it on. If you want to do things this way, you would have to write your own operator<< overloads and use std::function objects to wrap member fcns and the objects to be used to call them. A raw instance method can't be called without using an object. operator<< is a binary operator, so it can't be overloaded to take – Avi Berger May 14 '23 at 22:06
  • 2
    . . . 1) a stream object, 2)some object type of your own and 3)a member fcn ptr of that object type. SO this won't work because you are trying to use an operator<< that doesn't exist, you haven't written, and can't be written exactly this way as it would require template of a (non-existent) trinary operator<<. – Avi Berger May 14 '23 at 22:06
  • Ahh, that was one of the pieces I was missing. So the << foo << ... is really overloading the << operator. I wasnt aware of this since my static foo function I used was not overloading this operator (intentionally) so I assumed it would be something else. Thanks for clarification! – Tobias Reich May 14 '23 at 22:27
  • 2
    It's using an overload (not creating one). There is a std overload for fcns taking a single ostream & parameter and returning a ostream &. So your free function Foo works. Instance functions automatically take an additional, hidden parameter that is a pointer to a specific instance, so their types (and pointer to them) are different than and not compatible with free or class (static member) functions. So they won't fit the same overload from std. @SilvioMayolo's lambda combines and adapts object + member function to a signature that can match that overload. – Avi Berger May 14 '23 at 22:49
  • 2
    Correction: Actually, SilvioMayolo's lambda attempts to adapt things, it doesn't actually succeed at it - the types are still wrong. – Avi Berger May 15 '23 at 18:01

1 Answers1

0

The problem is that your standalone foo is a function, whereas b->foo is, according to C++, not one. Namely, the latter binds its this argument, so it requires some extra data on top of just the function's behavior. You can make it a valid std::function with an explicit lambda.

cout << 123 << [&b_object](ostream& out) { return b_object->foo(out); } << endl;

and if that's your only intended use case for this function, then you can return it from B::Foo directly.

// Be careful with lifetimes! The return value cannot
// outlive `this`!
std::function<ostream&(ostream&)> B::foo() {
  return [&this](ostream& out) { out << "my foo is " << my_foo; return out; };
}

Then you can write

cout << 123 << b_object->foo() << endl;

But, as indicated in the comments, it's much more common to use a specialized datatype for this. If the property you're trying to print is cheap, then just return it or a reference to it from foo() and you're done. If not, or if you want some complicated custom printing operation to happen, I recommend writing your own custom class and encapsulating this custom print behavior.

class ViewOfObjectB {
private:
  B& owner;
public:
  ViewOfObjectB(B& owner) : owner(owner) {}
  friend ostream& operator<<(ostream&, const ViewOfObjectB&);
};

ostream& operator<<(ostream& out, ViewOfObjectB& obj) {
  // Whatever you want to print goes here ...
  return out;
}

class B {
public:
  ViewOfObjectB foo() {
    return ViewOfObjectB(&this);
  }
};

Same idea as using a lambda, but since there's a ton of hidden complexity, we make that complexity explicit with a named view class that we can document independently as our code grows and becomes more complicated. You still call it using

cout << 123 << b_object->foo() << endl;

All of these calls are static (in the sense of "no vtable involved", not in the sense of the static keyword), so a clever optimizer should be able to inline it all for you.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • Thanks, gonna think about this a little bit in order to understand the problem a little better. But could you explain a little bit further why b->foo is no function (especially since it normally would be one). I'm not sure I understood the "binding the this argument". – Tobias Reich May 14 '23 at 22:22
  • 2
    When you write `b->foo(x, y, z)`, that's a lot like writing `B::foo(b, x, y, z)` (in some languages like Rust or Python, those are actually equivalent. In C++, it's distinct, but conceptually similar). The difference is that, for a free-standing function, you just want the function, but in your `b->foo` case, you want "the function plus its first argument fixed", so what you want is the function `B::foo(b, ?, ?, ?)`, which is more complex. And C++ tries to follow a zero overhead policy, so that complexity shouldn't be hidden behind a seemingly simple line of code. – Silvio Mayolo May 15 '23 at 00:12
  • I think the closest possible to what OP wants is [this](https://coliru.stacked-crooked.com/a/eb049558a6608cf3) which relies on the super dangerous hack of storing the `this` pointer temporarily in a static global. – Mooing Duck May 15 '23 at 16:45
  • As for the code in the answer here, it contains several typos, and since there is no `operator<<` that takes a `function` on the right hand side, it doesn't work. https://coliru.stacked-crooked.com/a/904dd52462c03dda – Mooing Duck May 15 '23 at 16:50
  • The part about using a view is the best answer though – Mooing Duck May 15 '23 at 16:51