Why does std::reference_wrapper not accept

2019-04-20 04:36发布

Normally, rvalues can bind to const references (const SomeType&). It's built into the language. However, std::reference_wrapper<const T> does not accept an rvalue as its constructor argument since the corresponding overload is deliberately deleted. What is the reason for this inconsistency? std::reference_wrapper is "advertised" as the alternative to a reference variable for cases when we must pass by value but would like to preserve reference semantics.

In other words, if the rvalue to const & binding is considered safe, since it's built into the language, why did the designers of C++11 not allow rvalues to be wrapped in std::reference_wrapper<const T>?

When would this come handy, you may ask. For example:

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
}

4条回答
Ridiculous、
2楼-- · 2019-04-20 04:40

INTRODUCTION

Normally T const& and T&& can extend the lifetime of a temporary directly bound to it, but this is not applicable if the reference is "hiding" behind a constructor.

Since std::reference_wrapper is copyable (by intention), the handle to the referenced object can outlive the temporary if the std::reference_wrapper is used in such a way that the handle escapes the scope where the temporary is created.

This will lead to a lifetime mismatch between the handle and the referred to object (ie. the temporary).


LET'S PLAY "MAKE BELIEVE"

Imagine having the below, illegal, snippet; where we pretend that std::reference_wrapper has a constructor that would accept a temporary.

Let's also pretend that the temporary passed to the constructor will have its lifetime extended (even though this isn't the case, in real life it will be "dead" right after (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!
}

Since a temporary is created with automatic storage duration, it will be allocated on the storage bound to the scope inside get_ref.

When get_ref later returns, our temporary will be destroyed. This means that our val in main would refer to an invalid object, since the original object is no longer in existance.

The above is the reason why std::reference_wrapper's constructor doesn't have an overload that accepts temporaries.


ANOTHER EXAMPLE

struct A {
  A (std::string const& r)
    : ref (r)
  { }

  std::string const& ref;
};

A foo { std::string { "temporary " } };

foo.ref = ...; // DANGLING REFERENCE!

The lifetime of std::string { "temporary" } will not be extended, as can be read in the standard.

12.2p5 Temporary objects [class.temporary]

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

  • A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.

  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

  • The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

    • A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.

Note: it's important to note that a constructor is nothing more than a "fancy" function, called upon object construction.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-04-20 04:41

You should not copy a const reference as it does not necessarily keep the referenced object alive. The following code shows the problem:

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

The const reference keeps the temporary X{3} alive for the constructor of Foo, but not for the object itself. You get a dangling reference.

To protected you from this problem the usage of temporary objects with std::reference_wrapper and std::ref has been disabled.

查看更多
可以哭但决不认输i
4楼-- · 2019-04-20 04:42

Since a const T&& variable can nor be moved, neither modified, there's no reason for using it (there's no adventages or differences over a const T&). Even more, a posterior use of that reference can be dangerous if the corresponding temporary is no longer alive (actually, it's dangerous, because it invokes undefined behaviour in such a case).

The deletion of its rvalue-reference constructor is not a bad idea after all.

查看更多
Juvenile、少年°
5楼-- · 2019-04-20 05:00

Binding a temporary directly to a reference prolongs its lifetime.

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
}

However, it must bind directly to the temporary. Consider the following example:

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
}

Which would make it quite useless to wrap a temporary, even if you could.

Note: an actual reference wrapper object uses a pointer, so that it can be reassigned:

struct Baz {
    Baz(const Foo& f): p(std::addressof(f)) {}
    Baz& operator=(const Foo& f) { p = std::addressof(f); return *this; }
    const Foo* p;
};

The same still applies: the temporary passed into the constructor or assignment operator will be destroyed at the end of the full-expression containing the call.

查看更多
登录 后发表回答