Why is there a need to have std::reference_wrapper
? Where should it be used? How is it different from a simple pointer? How its performance compares to a simple pointer?
std::reference_wrapper
is useful in combination with templates. It refers to an object by storing a pointer to it, allowing for reassignment and copy while mimicking reference (lvalue) semantics. It also instructs certain library templates to store references instead of objects.
Consider the algorithms in the STL which copy functors: You can avoid that copy by simply passing a reference wrapper referring to the functor instead of the functor itself:
unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state
This works because…
…
reference_wrapper
s overloadoperator()
so they can be called just like the function objects they refer to:std::ref(myEngine)() // Valid expression, modifies myEngines state
…(un)like ordinary references, copying (and assigning)
reference_wrappers
just assigns the pointee.int i, j; auto r = std::ref(i); // r refers to i r = std::ref(j); // Okay; r refers to j r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>
Copying a reference wrapper is practically equivalent to copying a pointer, which is as cheap as it gets. All the function calls inherent in using it (e.g. the ones to operator()
) should be just inlined as they are one-liners.
reference_wrapper
s are created via std::ref
and std::cref
:
int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>
The template argument specifies the type and cv-qualification of the object referred to; r2
refers to a const int
and will only yield a reference to const int
. Calls to reference wrappers with const
functors in them will only call const
member function operator()
s.
Rvalue initializers are disallowed, as permitting them would do more harm than good. Since rvalues would be moved anyway (and with guaranteed copy elision even that's avoided partly), we don't improve the semantics; we can introduce dangling pointers though, as a reference wrapper does not extend the pointee's lifetime.
Library interaction
As mentioned before, one can instruct make_tuple
to store a reference in the resulting tuple
by passing the corresponding argument through a reference_wrapper
:
int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
// Type of t2 is tuple<int&>
Note that this slightly differs from forward_as_tuple
: Here, rvalues as arguments are not allowed.
std::bind
shows the same behavior: It won't copy the argument but store a reference if it is a reference_wrapper
. Useful if that argument (or the functor!) need not be copied but stays in scope while the bind
-functor is used.
Difference from ordinary pointers
There is no additional level of syntactical indirection. Pointers have to be dereferenced to obtain an lvalue to the object they refer to;
reference_wrapper
s have an implicit conversion operator and can be called like the object they wrap.int i; int& ref = std::ref(i); // Okay
reference_wrapper
s, unlike pointers, don't have a null state. They have to be initialized with either a reference or anotherreference_wrapper
.std::reference_wrapper<int> r; // Invalid
A similarity are the shallow copy semantics: Pointers and
reference_wrapper
s can be reassigned.
std::make_tuple(std::ref(i));
superior to std::make_tuple(&i);
in some way? –
Surrebutter i
, not a reference to it. –
Egression not_null
. –
Egression not_null
such that passing a null pointer to it becomes invalid –
Ennead not_null
is preferable? I don't see it, but I'm not known for being very imaginative... –
Scaler reference_wrappers, unlike pointers, don't have a null state. They have to be initialized with either a reference or another reference_wrapper.
Since C++17 you can use a std::optional
to have a reference without initialization: std::optional<std::reference_wrapper<int>> x; auto y = 4; x = y;
Accessing to it is a bit verbose though: std::cout << x.value().get();
–
Trisyllable optional<int&>
is better? –
Egression There are, at least, two motivating purposes of std::reference_wrapper<T>
:
It is to give reference semantics to objects passed as value parameter to function templates. For example, you may have a large function object you want to pass to
std::for_each()
which takes its function object parameter by value. To avoid copying the object, you can usestd::for_each(begin, end, std::ref(fun));
Passing arguments as
std::reference_wrapper<T>
to anstd::bind()
expression is quite common to bind arguments by reference rather than by value.When using an
std::reference_wrapper<T>
withstd::make_tuple()
the corresponding tuple element becomes aT&
rather than aT
:T object; f(std::make_tuple(1, std::ref(object)));
fun
is a function object (i.e. an object of a class with a function call operator) and not a function: if fun
happens to be an actual function, std::ref(fun)
have no purpose and make the code potentially slower. –
Ileanaileane Another difference, in terms of self-documenting code, is that using a reference_wrapper
essentially disavows ownership of the object. In contrast, a unique_ptr
asserts ownership, while a bare pointer might or might not be owned (it's not possible to know without looking at lots of related code):
vector<int*> a; // the int values might or might not be owned
vector<unique_ptr<int>> b; // the int values are definitely owned
vector<reference_wrapper<int>> c; // the int values are definitely not owned
reference_wrapper
is superior to raw pointers not only because it is clear that it is non-owning, but also because it cannot be nullptr
(without shenanigans) and thus users know they can't pass nullptr
(without shenanigans) and you know you don't have to check for it. –
Scaler reference_wrapper
. But creating/using such references have undefined behaviour, hence my - overly generous! - labelling of it as "shenanigans". See Is null reference possible? –
Scaler You can think of it as a convenience wrapper around references so that you can use them in containers.
std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile
It's basically a CopyAssignable
version of T&
. Any time you want a reference, but it has to be assignable, use std::reference_wrapper<T>
or its helper function std::ref()
. Or use a pointer.
Other quirks: sizeof
:
sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24
And comparison:
int i = 42;
assert(std::ref(i) == std::ref(i)); // ok
std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error
std::vector<std::reference_wrapper<T>> vec;
instead of std::vector<T*> vec;
? –
Surrebutter reference_wrapper
code, making it identical to code that uses a pointer or reference. –
Ezar std::reference_wrapper
has the guarantee that the object is never null. Consider a class member std::vector<T *>
. You have to examine all of the class code to see if this object can ever store a nullptr
in the vector, whereas with std::reference_wrapper<T>
, you are guaranteed to have valid objects. –
Ezar reference_wrapper
must refer to its object via a pointer (because it must be assignable, otherwise we wouldn't need this class, etc.), and that's all it needs. (2) A real reference has no sizeof
according to the language, so the operator always returns the sizeof
the referred type (the fact that a reference within an object is implemented as a pointer and evident in the sizeof
said object is an implementation detail). –
Scaler vector<int> v;
, std::vector x{v, v}
and std::vector y{v}
have different types. That's true by definition. But also a quirk of CTAD rules. –
Trudey std::ref(s) == std::ref(s)
compile, although std::ref(i) == std::ref(i)
is ok? –
Quiberon © 2022 - 2024 — McMap. All rights reserved.
.
with instead of->
– Spyglass.
doesn't work the way you suggest it does (unless at some point the operator dot proposal is adopted and integrated :) ) – Egressionget()
member-function or with its implicit conversion back to the underlying type. – Advertisement