-1

Binding a non-const rvalue to a rvalue with member operators work (case A), but binding to a non-member operator (case B) does not:

struct A
{
    A & operator<<(int i) { return *this; }
};

struct B
{
};
inline B & operator<<(B & b, int i) { return b; }

int main()
{
    A() << 3; // OK
    B() << 3; // error: cannot bind non-const lvalue reference of type 'B&' to an rvalue of type 'B'
}

Why is this difference and how can I make the non-member operator work for non-const rvalues?

hpc64
  • 35
  • 5
  • 1
    You can call a non-const member function on a temporary because this does not involve binding of a reference. You cannot do that with a non-member function that accepts an lvalue reference. If you want to work with rvalues, perhaps use an rvalue reference. – n. m. could be an AI Apr 14 '23 at 22:55
  • `inline B& operator<<(B&& b, int) { return b; }` – Eljay Apr 14 '23 at 23:03
  • Yes, `inline B & operator<<(B && b, int i) { return b; }` works. What is the downside of using an rvalue reference? Why does `operator<<` for `std::ostream` not use it in general? – hpc64 Apr 14 '23 at 23:05
  • If only `inline B & operator<<(B && b, int i) { return b; }` is defined then `B b; b << 3;` results in `cannot bind rvalue reference of type 'B&&' to lvalue of type 'B'`. So both have to be defined. Is there a way to define only one? – hpc64 Apr 14 '23 at 23:16
  • Yes, in C++23 you can define only one. – Eljay Apr 14 '23 at 23:21
  • I'm not sure what feature Eljay refers to, but in any case, before that the only way to have one function would be to accept argument by value (and thus make a copy of `B`). And that also answers the question "Why does operator<< for std::ostream not use it in general?" - because it's not useful. I've never encountered any case where I'd want to call `operator <<` on rvalue of `std::ostream`. Creating streams is usually an expensive operation, I don't want to use throwaway temporaries for that. – Yksisarvinen Apr 14 '23 at 23:22
  • @Yksisarvinen: This is exactly my use case: `struct Buffer { Stream getStream(); }; Buffer b; b.getStream() << 3;`. The overhead to create the Stream it is every small. – hpc64 Apr 14 '23 at 23:36
  • 1
    @Eljay: Interesting! How is it done in C++23? – hpc64 Apr 14 '23 at 23:37

1 Answers1

1

As pointed out in the comments an additional operator overload using an rvalue can be used: inline B & operator<<(B && b, int i) { return b; }

Another solution that requires only 1 operator<< is

template<typename T>
struct Ref
{
    T & ref;
    Ref( T & t) : ref(t) {}
    Ref( T && t) : ref(t) {}
};

//Instead of
//inline B & operator<<(B && b, int i) { return b; }
//inline B & operator<<(B & b, int i) { return b; }
//just:
inline B & operator<<(Ref<B> b, int i) { return b.ref; }
hpc64
  • 35
  • 5