Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent expression templates binding to rvalue references

I understand that doing something like the following:

auto&& x = Matrix1() + Matrix2() + Matrix3();
std::cout << x(2,3) << std::endl;

Will cause a silent runtime error if the matrix operations use expression templates (such as boost::ublas).

Is there any way of designing expression templates to prevent the compiler from compiling such code that may result in the use of expired temporaries at runtime?

(I've attempted unsuccessfully to work around this issue, the attempt is here)

like image 458
Clinton Avatar asked Mar 02 '12 04:03

Clinton


People also ask

How do I bind an rvalue reference to a temporary value?

In this example, the rvalue reference a can be bound to the temporary initialized with the rvalue expression 2, but the rvalue reference b cannot be bound to the lvalue expression i. You can bind the rvalue reference c to the temporary value 1.0 that is converted from the variable i.

What is binding R-value to L-value in C++?

Binding r-value to l-value reference is non-standard Microsoft C++ extension 3 Why does a friend operator require const-ness on its operands while a member operator does not? 1 Why is const rvalue reference implicitly converted into const reference?

What is an rvalue reference?

And an rvalue reference is a reference that binds to an rvalue. rvalue references are marked with two ampersands (&&). Note that there is one exception: there can be lvalue const reference binding to an rvalue.

Why is X not an rvalue reference?

It is common to misunderstand x as a rvalue reference when using it inside a function body like above. Here inside function body x itself is not an rvalue reference object because it has a name and named rvalue is an lvalue. && only implies that it is OK to dispose ofx.


2 Answers

Is there any way of designing expression templates to prevent the compiler from compiling such code that may result in the use of expired temporaries at runtime?

No. This was actually recognized before C++11's final standardization, but I don't know if it was ever brought to the committee's notice. Not that a fix would have been easy. I suppose the simplest thing would be a flag on types that would simply error if auto tries to deduce it, but even that would be complex because decltype can also deduce it, as well as template argument deduction. And all three of these are defined in the same way, but you probably don't want the latter to fail.

Just document your library appropriately and hope that nobody tries to capture them that way.

like image 72
Nicol Bolas Avatar answered Nov 15 '22 17:11

Nicol Bolas


As I understnad, root of your problem is that expression template temporary may have references/pointers to some another temporaries. And by using auto&& we only extend life of expression template temporary itself, but not lifetime of temporaries it has references to. Is it right?

For instance, is this your case?

#include <iostream>
#include <deque>
#include <algorithm>
#include <utility>
#include <memory>
using namespace std;

deque<bool> pool;

class ExpressionTemp;
class Scalar
{
    bool *alive;

    friend class ExpressionTemp;

    Scalar(const Scalar&);
    Scalar &operator=(const Scalar&);
    Scalar &operator=(Scalar&&);
public:
    Scalar()
    {
        pool.push_back(true);
        alive=&pool.back();
    }
    Scalar(Scalar &&rhs)
        : alive(0)
    {
        swap(alive,rhs.alive);
    }
    ~Scalar()
    {
        if(alive)
            (*alive)=false;
    }
};
class ExpressionTemp
{
    bool *operand_alive;
public:
    ExpressionTemp(const Scalar &s)
        : operand_alive(s.alive)
    {
    }
    void do_job()
    {
      if(*operand_alive)
          cout << "captured operand is alive" << endl;
      else
          cout << "captured operand is DEAD!" << endl;
    }
};

ExpressionTemp expression(const Scalar &s)
{
    return {s};
}
int main()
{
    {
        expression(Scalar()).do_job(); // OK
    }
    {
        Scalar lv;
        auto &&rvref=expression(lv);
        rvref.do_job(); // OK, lv is still alive
    }
    {
        auto &&rvref=expression(Scalar());
        rvref.do_job(); // referencing to dead temporary
    }
    return 0;
}

If yes then one of possible solutions, is to make special kind of expression template temporaries which hold resources moved from temporaries.

For instance, check this approach (you may define BUG_CASE macro, to get again bug case).

//#define BUG_CASE

#include <iostream>
#include <deque>
#include <algorithm>
#include <utility>
#include <memory>
using namespace std;

deque<bool> pool;

class ExpressionTemp;
class Scalar
{
    bool *alive;

    friend class ExpressionTemp;

    Scalar(const Scalar&);
    Scalar &operator=(const Scalar&);
    Scalar &operator=(Scalar&&);
public:
    Scalar()
    {
        pool.push_back(true);
        alive=&pool.back();
    }
    Scalar(Scalar &&rhs)
        : alive(0)
    {
        swap(alive,rhs.alive);
    }
    ~Scalar()
    {
        if(alive)
            (*alive)=false;
    }
};
class ExpressionTemp
{
#ifndef BUG_CASE
    unique_ptr<Scalar> resource; // can be in separate type
#endif
    bool *operand_alive;
public:
    ExpressionTemp(const Scalar &s)
        : operand_alive(s.alive)
    {
    }
#ifndef BUG_CASE
    ExpressionTemp(Scalar &&s)
        : resource(new Scalar(move(s))), operand_alive(resource->alive)
    {
    }
#endif
    void do_job()
    {
      if(*operand_alive)
          cout << "captured operand is alive" << endl;
      else
          cout << "captured operand is DEAD!" << endl;
    }
};

template<typename T>
ExpressionTemp expression(T &&s)
{
    return {forward<T>(s)};
}
int main()
{
    {
        expression(Scalar()).do_job(); // OK, Scalar is moved to temporary
    }
    {
        Scalar lv;
        auto &&rvref=expression(lv);
        rvref.do_job(); // OK, lv is still alive
    }
    {
        auto &&rvref=expression(Scalar());
        rvref.do_job(); // OK, Scalar is moved into rvref
    }
    return 0;
}

Your operator/function overloads may return different types, depending on T&&/const T& arguments:

#include <iostream>
#include <ostream>
using namespace std;

int test(int&&)
{
    return 1;
}
double test(const int&)
{
    return 2.5;
};

int main()
{
    int t;
    cout << test(t) << endl;
    cout << test(0) << endl;
    return 0;
}

So, when your expression template temporary do not have resources moved from temporaries - it's size will be not affected.

like image 42
Evgeny Panasyuk Avatar answered Nov 15 '22 16:11

Evgeny Panasyuk