Запретить привязку шаблонов выражений к ссылкам rvalue
Я понимаю, что делаю что-то вроде следующего:
auto&& x = Matrix1() + Matrix2() + Matrix3();
std::cout << x(2,3) << std::endl;
Будет вызывать тихую ошибку времени выполнения, если матричные операции используют шаблоны выражений (такие как boost::ublas
).
Есть ли способ разработки шаблонов выражений, чтобы компилятор не компилировал такой код, который может привести к использованию временных файлов с истекшим сроком действия во время выполнения?
(Я безуспешно пытался обойти эту проблему, попытка здесь)
2 ответа
Есть ли способ разработки шаблонов выражений, чтобы компилятор не компилировал такой код, который может привести к использованию временных файлов с истекшим сроком действия во время выполнения?
Нет. Это было фактически признано до окончательной стандартизации C++11, но я не знаю, было ли это когда-либо доведено до сведения комитета. Не то чтобы исправить это было бы легко. Я предполагаю, что самым простым будет флаг на типах, который будет просто ошибкой, если auto
пытается вывести это, но даже это было бы сложно, потому что decltype
также можно вывести его, а также вывести аргумент шаблона. И все три из них определены одинаково, но вы, вероятно, не хотите, чтобы последний потерпел неудачу.
Просто документируйте свою библиотеку соответствующим образом и надейтесь, что никто не попытается захватить их таким образом.
Как я понимаю, корень вашей проблемы в том, что временный шаблон выражения может иметь ссылки / указатели на другие временные символы. А с помощью 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;
}
Перегрузки вашего оператора / функции могут возвращать разные типы, в зависимости от аргументов 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;
}
Таким образом, когда ваш шаблон выражения временно не перемещает ресурсы из временных объектов - это не повлияет на его размер.