Domenic
Domenic

Reputation: 112897

Should my generic template use T or T&&?

I am working in a file that has a given function with many overloads, like so:

inline X f(ScriptWrappable* impl, Y y, Z z) { ... }
inline X f(Node* impl, Y y, Z z) { ... }
inline X f(RawPtr<T> impl, Y y, Z z) { ... }
inline X f(const RefPtr<T>& impl, Y y, Z z) { ... }
inline X f(ScriptWrappable* impl, Y y, Z z) { ... }
inline X f(const String& impl, Y y, Z z) { ... }
inline X f(int64_t impl, Y y, Z z) { ... }
template<typename T, size_t capacity> inline X f(const Vector<T, capacity>& impl, Y y, Z z) { ... }

I am trying to add a new overload that only requires one parameter, like so:

template<typename T> inline X f(T impl, W w) {
  return f(impl, w->getY(), w->getZ());
}

I am using templates so that all of the above variations automatically work with my new two-parameter version.

However, during code review I was asked, "Is T&& better for avoiding copy?". That is, should I instead do

template<typename T> inline X f(T&& impl, W w) {
  return f(impl, w->getY(), w->getZ());
}

I don't really know the answer to this question. I thought I understood universal references, but I am not familiar with when they are a good idea or not. What would be the consequences in my situation of choosing one over the other?

If I had to guess, I'd say that since the original types that T can stand in for are all simple to copy (primitives, references, or pointers) T&& will not give much benefit. But I'm still curious if e.g. any types could be passed to the T&& version that could not be passed to the T version, or vice versa.

Upvotes: 6

Views: 237

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69912

The answer is a little complex because it requires some understanding of the compiler's rules regarding templates.

In the following declaration:

template<class T> void func(T&& t);

T is evaluated in deduced context and as a result will be treated by the compiler either as an r-value reference or a l-value reference, whichever is appropriate. Coupled with the use of std::forward (note: in this case not std::move) this results in perfect forwarding and is optimally efficient.

However, this is not being evaluated in deduced context:

template<class T>
struct X {
  void foo(T&& t);
};

In this case, foo is in fact demanding an r-value reference.

Neither is this deduced context:

template<class T>
void foo(std::vector<T>&& v);

In these two cases, you should use std::move.

Upvotes: 3

Tobias Brandt
Tobias Brandt

Reputation: 3413

You should write your function like this:

template<typename T> inline X f(T&& impl, W w) {
  return f(std::forward<T>(impl), w->getY(), w->getZ());
}

This is called perfect forwarding. The type of impl will be exactly the same as if you had called f directly. It will not necessarily be an r-value reference.

Upvotes: 8

Related Questions