Запретить привязку шаблонов выражений к ссылкам rvalue - PullRequest
9 голосов
/ 02 марта 2012

Я понимаю, что выполнение чего-то вроде следующего:

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

вызовет тихую ошибку времени выполнения, если в матричных операциях используются шаблоны выражений (например, boost::ublas).

Есть ли способ разработки шаблонов выражений, чтобы компилятор не компилировал такой код, который может привести к использованию временных файлов с истекшим сроком действия во время выполнения?

(Я безуспешно пытался обойти эту проблему,попытка здесь )

Ответы [ 2 ]

7 голосов
/ 02 марта 2012

Есть ли способ разработки шаблонов выражений, чтобы компилятор не компилировал такой код, который может привести к использованию временных файлов с истекшим сроком действия во время выполнения?

Нет. Это было фактически признано до окончательной стандартизации C ++ 11, но я не знаю, было ли это когда-либо доведено до сведения комитета. Не то чтобы исправить это было бы легко. Я предполагаю, что самой простой вещью будет флаг для типов, который просто выдаст ошибку, если auto попытается вывести его, но даже это будет сложно, потому что decltype также может вывести его, как и вывод аргумента шаблона. И все три из них определены одинаково, но вы, вероятно, не хотите, чтобы последний потерпел неудачу.

Просто документируйте свою библиотеку надлежащим образом и надейтесь, что никто не попытается захватить их таким образом.

2 голосов
/ 21 октября 2012

Как я понял, корень вашей проблемы в том, что временный шаблон выражения может иметь ссылки / указатели на некоторые другие временные символы.А с помощью auto && мы только продлеваем срок жизни самого шаблона выражения, но не время жизни временных ссылок, на которые он ссылается.Это правильно?

Например, это ваш случай?

#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;
}

Если да, то одно из возможных решений - это сделать специальный вид временных шаблонов выраженийкоторые содержат ресурсы, перемещенные из временных.

Например, отметьте этот подход (вы можете определить макрос 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;
}

Ваш операторПерегрузки / function могут возвращать различных типов , в зависимости от аргументов T && / const T &:

#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;
}

Таким образом, когда ваш шаблон выражения временно не имеет ресурсов, перемещенных из временных файлов - его размер будетне влияет.

...