Почему 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 не является плохой идеей.