Reputation: 112897
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
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
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