When is the use of std::ref necessary? [duplicate]
Asked Answered
I

4

49

Consider:

std::tuple<int , const A&> func (const A& a) 
{
  return std::make_tuple( 0 , std::ref(a) );
}

Is the std::ref required for writing correct and portable code? (It compiles fine without it)

Background:

If I remove std::ref my code builds fine without any warnings (g++-4.6 -Wall), but doesn't run correctly.

In case of interest the definition of A:

struct A {
  std::array<int,2> vec;
  typedef int type_t;

  template<typename... OPs,typename... VALs>
  A& operator=(const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& e) {
    for( int i = 0 ; i < vec.size() ; ++i ) {
      vec[i] = eval( extract(i,e.first) , e.second );
    }
  }
};
Implicatory answered 6/8, 2012 at 17:30 Comment(1)
Also nicely asked and answered in #15530960Theodore
H
25
  • make_tuple(0, a) makes a tuple<int, A>.
  • make_tuple(0, ref(a)) makes a tuple<int, reference_wrapper<A>>.
  • You can also say tuple<int, A&> t(0, a); for a tuple you can't make with make_tuple, or use std::tie.
Halting answered 6/8, 2012 at 17:32 Comment(7)
So, it should throw an error if ref is removed. Why does it not, any idea?Implicatory
@Frank: A tuple can be constructed from anything humanly possible. You just end up returning a reference to a local variable, namely a local copy of a. With ref you return a wrapped reference to the original function argument.Halting
@Frank: in other words, you should say return std::tuple<int, A const &>(0, a);.Halting
I see. But then, as you say, a tuple<int,A> is created/returned. How can this be returned given the tuple<int,const A&> as the return type?Implicatory
I did :-) That's twisted. In the function body a refers to the instance passed into the function. But when using make_tuple it is treated as a local variable. That's error-prone. Any advice how to avoid such erroneous practice? ;-)Implicatory
@Frank: Know your way around how std::tuple works? It's a fascinating little template that touches on so many corners of C++...Halting
Strictly speaking std::make_tuple(ref(a)) would result in an std::tuple<A const&>, not std::tuple<std::reference_wrapper<const A>>. Many parts of the Standard library that decay their arguments are special-cased to 'decay' std::reference_wrapper<T> to T&. They can be identified because they're often specified in terms of std::decay even though std::decay itself doesn't do that conversion (somewhat frustratingly).Dorsett
E
49

One of the example where std::ref is necessary:

void update(int &data)  //expects a reference to int
{
    data = 15;
}
int main()
{
    int data = 10;

    // This doesn't compile as the data value is copied when its reference is expected.
    //std::thread t1(update, data);         

    std::thread t1(update, std::ref(data));  // works

    t1.join();
    return 0;
}

The std::thread constructor copies the supplied values, without converting to the expected argument type (which is reference type in this case, seeupdate()). So we need to wrap the arguments that really needs to be references in std::ref.

Essam answered 22/8, 2017 at 17:19 Comment(2)
std::thread t1(update, data); will compiles well, but the problem is that it does not change the value of the data.Feline
@MohammadRoohitavaf It doesn't compile! The variable "data" must be wrapped in std::ref(data) to generate a rvalue being passed to t1. You would be right, if the parameter "data" in function "update" would be of type "const int&". But then std::thread would internally copy the argument and actually not reference the passed variable "data".Envelopment
A
27

std::ref does not make a reference, so in your code sample it doesn't do what you expect. std::ref creates an object that behaves similarly to a reference. It may be useful, for example, when you want to instantiate a functor, and pass a reference-like version of it to a standard library algorithm. Since algorithms take functors by value, you can use std::ref to wrap the functor.

Angling answered 6/8, 2012 at 17:35 Comment(0)
H
25
  • make_tuple(0, a) makes a tuple<int, A>.
  • make_tuple(0, ref(a)) makes a tuple<int, reference_wrapper<A>>.
  • You can also say tuple<int, A&> t(0, a); for a tuple you can't make with make_tuple, or use std::tie.
Halting answered 6/8, 2012 at 17:32 Comment(7)
So, it should throw an error if ref is removed. Why does it not, any idea?Implicatory
@Frank: A tuple can be constructed from anything humanly possible. You just end up returning a reference to a local variable, namely a local copy of a. With ref you return a wrapped reference to the original function argument.Halting
@Frank: in other words, you should say return std::tuple<int, A const &>(0, a);.Halting
I see. But then, as you say, a tuple<int,A> is created/returned. How can this be returned given the tuple<int,const A&> as the return type?Implicatory
I did :-) That's twisted. In the function body a refers to the instance passed into the function. But when using make_tuple it is treated as a local variable. That's error-prone. Any advice how to avoid such erroneous practice? ;-)Implicatory
@Frank: Know your way around how std::tuple works? It's a fascinating little template that touches on so many corners of C++...Halting
Strictly speaking std::make_tuple(ref(a)) would result in an std::tuple<A const&>, not std::tuple<std::reference_wrapper<const A>>. Many parts of the Standard library that decay their arguments are special-cased to 'decay' std::reference_wrapper<T> to T&. They can be identified because they're often specified in terms of std::decay even though std::decay itself doesn't do that conversion (somewhat frustratingly).Dorsett
B
3

Answering the question in the title (When is the use of std::ref necessary?): Another case where std::ref is useful is when looping over a list of references to objects and modify them:

std::vector<int> v1, v2;
  
void test() {
  for (std::vector<int>& vv : 
    // Compiles
    { std::ref(v1), std::ref(v2) } 
  
    // Compiler rejects this with:
    //   binding reference of type 'vector<...>' to value of 
    //   type 'const vector<...>' drops 'const' qualifier 
    // { v1, v2} 
  ) {
      vv.push_back(3);
  }
}

Without using std::ref in the list, the objects are treated as const and can't be modified (see also https://godbolt.org/z/Ta6YM31KM).

Bryce answered 23/12, 2021 at 11:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.