Search code examples
c++lifetimervalue

Can invoke C++ methods on temporary r-values, but can't pass the same to global functions


In the following code sample, class A defines an operator% that takes a string as a class member; and there's also a global operator % that takes an A& and an int. In the main, I can instantiate a temporary A and chain the member followed by the global, but not the other way round. Seems like both should work equally well if the standard/compiler just allowed me to pass the reference to the global operator% as in both cases I'm done with the temporary A at the end of the statement (which IIUC is when it gets destroyed):

#include <iostream>
using namespace std;
struct A {
    A& operator%(const string& s) {
        cout << s << endl;
        return *this;
    }
};
A& operator%(A& a, int i) {
    cout << i << endl;
    return a;
}

int main() {
    A() % "abc" % 42;
//  A() % 42 % "abc"; //  error: cannot bind non-const lvalue reference of type ‘A&’ to an rvalue of type ‘A’
    return 0;
}

This strikes me as rather imbalanced: I can chain as many global operators as I want as long as I start with a member operator to "hide" the fact that it all started with a temporary.

  1. Am I missing something? What was the motivation for this imbalance in permissibility?
  2. I can't actually modify A, so defining new member operators is not an option.
  3. Although my simplistic example code doesn't make it obvious, I can't use const A& as the operators do actually modify the A.
  4. I'd like to come up with a solution that allows me to "stream" to a temporary. What I'm actually doing is a little different, which I'll share briefly in hopes that the difference might allow some trick I'm not seeing. It's more like: a % b(...) % 42 % "abc"; where a is a normal l-valued instance of A (not temporary), b(...) is a manipulator that doesn't return an A&, but rather returns a stack allocated object of some third type C (by value). It's this C which is therefore temporary and for which I can't define new member operators, and to which I'm having trouble streaming 42 and "abc" (could be anything -- not just ints and strings).

I'm afeared I'm going to have to abandon this whole approach and actually define a variable of type C and then stream to the C on a separate code line.


Solution

  • Member functions are by default not constrained on value category, even though we can constrain them explicitly if need be:

    class C {
      void mem() &&;
    };
    

    That is because the implicit object parameter has special quirks that help predetermine the member being called (in member access, thought overloaded operators mimic that with some spec voodoo). Free functions however care very much about the value category of their arguments, since it drives overload resolution entirely.

    You can do something akin to what the standard library did and define a catch-all operator for rvalues:

    template<class T>
    A& operator%(A&& a, T const& t) {
      return a % t;
    }
    

    This essentially handles that of the chain regardless of position. Be warned however that this complicates overload resolution, so care might be needed to constrain this overload with sfinae/concetps down the line.