3

I am trying to make a system for lazy evaluation, however it seems my system wont work with rvalues/temporary variable. for example

class Number;

class LazyAddition
{
public:
    Number& lhs;
    Number& rhs;
    LazyAddition(Number& lhs, Number& rhs)
        : lhs(lhs), rhs(rhs)
    {

    }

    LazyAddition(Number&& lhs, Number&& rhs)
        : lhs(lhs), rhs(rhs)
    {

    }


};

class Number
{
public:
    Number(int x)
        : x(x)
    {

    }


    LazyAddition operator+(Number& rhs)
    {
        return LazyAddition(*this, rhs);
    }


    Number(LazyAddition lazy)
    {
        x = lazy.lhs.x + lazy.rhs.x;
    }

private:
    int x;
};

It works fine when passing in lvalues like

Number num1(3)
Number num2(4)
LazyAddition { num1, num2 }

it also function when passing in rvalues

LazyAddition { Number(3), Number(4) }

however after the constructor is called, the number rvalues are immediately destroyed. this is problematic since I have references to them. I thought assigning the rvalue reference to the lvalue reference in the LazyAddition constructor might expand the lifetime, but it doesn't. is there any way to achieve this, or a better way to not copy rvalues?

user3840170
  • 26,597
  • 4
  • 30
  • 62
  • In C++ language, the references don't hold any data. Furthermore, there is no any machine code generated in response on the `some_type& some_var = onther_var;` construction. You may think that the reference is the synonym of other variable. So, when you are trying to make the reference to the rvalue (let's say, to the temporary object), it'll be a broken reference. I.e., It'll refer to the wrong place in the memory, where some time ago was your rvalue. – Serge Roussak Sep 27 '21 at 06:46
  • Shouldn't it be `LazyAddition(num1, num2)` rather than `LazyAddition(num1 + num2)`? – xskxzr Sep 27 '21 at 07:01
  • @SergeRoussak "he references don't hold any data" afaik it's unspecified if references hold data. On some cases they don't on other that's impossible and a reference holds a pointer under the hood. "there is no any machine code generated in response on the `some_type& some_var = onther_var;`" again not true, depending on the scope of the variables there can be a nop or there can be a pointer assignment under the hood. – bolov Sep 27 '21 at 08:05

2 Answers2

0

Because the rvalues are bound to the parameters of the constructor firstly, and bounding them to the class members later does not further extend their lifetime.

You can define your LazyAddition class as an aggregate to avoid binding the rvalues to parameters of constructor:

struct LazyAddition
{
    const Number& lhs;
    const Number& rhs;
};

But be careful. After C++20, you should use list-initialization syntax to initialize such a LazyAddition aggregate, like LazyAddition { Number(3), Number(4) }, otherwise the lifetime will still not be extended.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • Please don't do this. Just store Number by value. Having it as an aggregate is good! But dear god, store it by value, because the way you have it right now it'll break when you try to return it from a function and it'll just be a whole mess. – Alecto Irene Perez Sep 27 '21 at 07:26
  • @AlectoIrenePerez It depends on OP's intent. Maybe the values after the construction of `LazyAddition` will change and the OP wants to catch such changes for "lazy" addition. – xskxzr Sep 27 '21 at 07:30
  • @AlectoIrenePerez it will be a compiler error instead of "just break", no? – 463035818_is_not_an_ai Sep 27 '21 at 07:48
  • 1
    The reason that something that is very common i.e. `LazyAddition(Number(1), Number(2))` fails silently is a major reason **not** to use this. It is so easy to create a nasty bug that is very difficult to track. – bolov Sep 27 '21 at 08:02
  • @bolov I think there is no problem as long as the user realizes that the class has reference semantic. Otherwise all classes containing reference members should be forbidden for the same reason. – xskxzr Sep 27 '21 at 08:30
  • @xskxzr " as long as the user realizes ..." yeah, that's a problem. Your class is too fragile. It can break too easily by doing something seemingly innocuous. – bolov Sep 27 '21 at 08:33
0

Reference members are problematic for several reasons. You need to store the Number somewhere, hence passing r-value reference cannot work your way.

I propose a completely different approach:

#include <functional>
#include <iostream>

class Number;
class LazyAddition
{
public:
    using Getter = std::function<Number(void)>;
    LazyAddition(Getter lhs,Getter rhs) : lhs(lhs), rhs(rhs) {}
    Getter lhs;
    Getter rhs;
};

class Number
{
public:
    Number(int x) : x(x) {}

    LazyAddition operator+(Number& rhs) {
        return LazyAddition([t=*this](){ return t; },[rhs](){return rhs;});
    }

    Number(LazyAddition lazy)
    {
        x = lazy.lhs().x + lazy.rhs().x;
    }
    int get() { return x;}
private:
    int x;
};

int main() {
    Number num1(3);
    Number num2(4);
    auto l = num1+num2;
    std::cout << Number(l).get();
}

This is actually more flexible, because you can create a LazyAddition even before you have a Number. All you need is a callable that will return the Number only when needed.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185