Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Universal reference argument used twice

I wish to create a wrapper around std::make_pair that takes a single argument and uses that argument to make the first and second members of the pair. Furthermore, I wish to take advantage of move semantics.

Naively, we might write (ignoring return types for clarity),

template <typename T>
void foo(T&& t)
{
  std::make_pair(std::forward<T>(t),
                 std::forward<T>(t));
}

but this is unlikely to do what we want.

What we want is:

  • In the case where foo is called with a (const) lvalue reference argument, we should pass that (const) reference on to std::make_pair unmodified for both arguments.
  • In the case where foo is called with an rvalue reference argument, we should duplicate the referenced object, then call std::make_pair with the original rvalue reference as well as an rvalue reference to the newly created object.

What I've come up with so far is:

template <typename T>
T forward_or_duplicate(T t)
{
  return t;
}

template <typename T>
void foo(T&& t)
{
  std::make_pair(std::forward<T>(t),
                 forward_or_duplicate<T>(t));
}

But I'm reasonably sure it's wrong.

So, questions:

  1. Does this work? I suspect not in that if foo() is called with an rvalue reference then T's move constructor (if it exists) will be called when constructing the T passed by value to forward_or_duplicate(), thus destroying t.

  2. Even if it does work, is it optimal? Again, I suspect not in that T's copy constructor will be called when returning t from forward_or_duplicate().

  3. This seems like a common problem. Is there an idiomatic solution?

like image 527
Christopher Key Avatar asked Feb 18 '26 02:02

Christopher Key


1 Answers

So, questions:

  1. Does this work? I suspect not in that if foo() is called with an rvalue reference then T's move constructor (if it exists) will be called when constructing the T passed by value to forward_or_duplicate(), thus destroying t.

No, t in foo is an lvalue, so constructing the T passed by value to forward_or_duplicate() from t calls the copy constructor.

  1. Even if it does work, is it optimal? Again, I suspect not in that T's copy constructor will be called when returning t from forward_or_duplicate().

No, t is a function parameter, so the return implicitly moves, and doesn't copy.

That said, this version will be more efficient and safer:

template <typename T>
T forward_or_duplicate(std::remove_reference_t<T>& t)
{
  return t;
}

If T is an lvalue reference, this results in the same signature as before. If T is not a reference, this saves you a move. Also, it puts T into a non-deduced context, so that you can't forget to specify it.

like image 157
T.C. Avatar answered Feb 21 '26 13:02

T.C.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!