Почему std:: reference_wrapper <const T> не принимает временное?
Обычно rvalues могут связываться с константными ссылками (const SomeType&
). Он встроен в язык. Однако std::reference_wrapper<const T>
не принимает значение rvalue в качестве аргумента конструктора, так как соответствующая перегрузка намеренно удаляется. В чем причина этой несогласованности? std::reference_wrapper
"рекламируется" как альтернатива ссылочной переменной для случаев, когда мы должны передавать по значению, но хотели бы сохранить ссылочную семантику.
Другими словами, если привязка rvalue к const &
считается безопасной, так как она встроена в язык, почему дизайнеры С++ 11 не разрешали rvalues быть заключены в std::reference_wrapper<const T>
?
Когда это пригодится, вы можете спросить. Например:
class MyType{};
class Foo {
public:
Foo(const MyType& param){}
};
class MultiFoo {
public:
MultiFoo(std::initializer_list<std::reference_wrapper<const MyType>> params){}
};
int main()
{
Foo foo{MyType{}}; //ok
MultiFoo multiFoo{MyType{}, MyType{}}; //error
}
Ответы
Ответ 1
Связывание временного непосредственно с ссылкой продлевает срок его службы.
struct Foo {};
Foo f() { return {}; }
void g() {
f(); // temporary destroyed at end of full-expression
const Foo& r = f(); // temporary destroyed at end of scope
// r can still be used here ...
// ...
// all the way down to here
}
Однако он должен привязываться непосредственно к временному. Рассмотрим следующий пример:
struct Bar {
Bar(const Foo& f): r(f) {}
const Foo& r;
};
void h() {
Bar b(f());
// binding occurs through parameter "f" rather than directly to temporary "f()"
// b.r is now a dangling reference! lifetime not extended
}
Это сделало бы совершенно бесполезным временное временное, даже если бы вы могли.
Примечание. В фактическом объекте ссылочной оболочки используется указатель, поэтому его можно переназначить:
struct Baz {
Baz(const Foo& f): p(std::addressof(f)) {}
Baz& operator=(const Foo& f) { p = std::addressof(f); return *this; }
const Foo* p;
};
То же самое относится: временный код, переданный в конструктор или оператор присваивания, будет уничтожен в конце полного выражения, содержащего вызов.
Ответ 2
Введение
Обычно T const&
и T&&
могут продлить время жизни временного, непосредственно связанного с ним, но это неприменимо, если ссылка скрывается за конструктором.
Так как std::reference_wrapper
является скопируемым (по желанию), дескриптор ссылочного объекта может пережить временный, если std::reference_wrapper
используется таким образом, что дескриптор не выполняет область, в которой создается временное.
Это приведет к несоответствию жизни между дескриптором и указанным объектом (т.е. временным).
LET PLAY "СДЕЛАТЬ ВЕРЮ"
Представьте себе, что ниже, незаконно, фрагмент; где мы делаем вид, что std::reference_wrapper
имеет конструктор, который принимает временный.
Пусть также притворяется, что временное, переданное конструктору, будет продлеваться на всю жизнь (хотя это не так, в реальной жизни оно будет "мертвым" сразу после (1)
).
typedef std::reference_wrapper<std::string const> string_ref;
string_ref get_ref () {
string_ref temp_ref { std::string { "temporary" } }; // (1)
return temp_ref;
}
int main () {
string_ref val = get_ref ();
val.get (); // the temporary has been deconstructed, this is dangling reference!
}
Так как временная создается с автоматической продолжительностью хранения, она будет выделена на хранение, привязанное к области внутри get_ref
.
Когда get_ref
позже вернется, наше временное будет уничтожено. Это означает, что наш val
в main
будет ссылаться на недопустимый объект, поскольку исходный объект больше не существует.
Вышесказанное является причиной того, что конструктор std::reference_wrapper
не имеет перегрузки, которая принимает временные.
ДРУГОЙ ПРИМЕР
struct A {
A (std::string const& r)
: ref (r)
{ }
std::string const& ref;
};
A foo { std::string { "temporary " } };
foo.ref = ...; // DANGLING REFERENCE!
Время жизни std::string { "temporary" }
не будет расширено, как можно прочитать в стандарте.
12.2p5
Временные объекты [class.temporary]
Временная привязка ссылки или временный объект, являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется для времени жизни ссылки, за исключением:
-
-
Временная привязка к ссылочному элементу в конструкторе ctor-initializer (12.6.2) сохраняется до завершения конструктора.
-
Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.
-
Время жизни временной привязки к возвращаемому значению в операторе return функции (6.6.3) не распространяется; временное уничтожается в конце полного выражения в операторе return.
-
- Временная привязка к ссылке в new-initializer (5.3.4) сохраняется до завершения полного выражения, содержащего новый-инициализатор.
Примечание: важно отметить, что конструктор - это не что иное, как "причудливая" функция, вызываемая конструкцией объекта.
Ответ 3
Вы не должны копировать ссылку на константу, поскольку она не обязательно сохраняет объект, на который ссылается объект. Следующий код показывает проблему:
#include <iostream>
struct X { int x; };
struct Foo {
const X& a;
Foo(const X& na) : a(na) {} // here na is still OK
};
int main()
{
Foo foo{X{3}}; // the temporary exists only for this expression
// now the temporary is no longer alive and foo.a is a dangling reference
std::cout << foo.a.x << std::endl; // undefined behavior
}
Ссылка const сохраняет временный X{3}
живой для конструктора Foo
, но не для самого объекта. Вы получаете свисающую ссылку.
Чтобы защитить вас от этой проблемы, использование временных объектов с помощью std::reference_wrapper
и std::ref
отключено.
Ответ 4
Так как переменная const T&&
не может быть перемещена, ни одна из них не модифицирована, нет никаких оснований для ее использования (нет никаких атак или различий по сравнению с const T&
). Более того, последующее использование этой ссылки может быть опасным, если соответствующее временное время больше не является живым (на самом деле, это опасно, потому что оно вызывает поведение undefined в таком случае).
Удаление его конструктора ссылок rvalue не является плохой идеей.