I am testing universal reference with the following code,
template <typename T>
vector<T> attach_(vector<T> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
int main() {
int k = 2;
attach_(std::move(vector<int>{1,2,3}),k); //not OK
attach_(std::move(vector<int>{1,2,3}),(int&)k); //not OK
attach_(std::move(vector<int>{1,2,3}),(int)k); //OK
attach_(std::move(vector<int>{1,2,3}),2); //OK
}
and got an error:
no matching function for call to 'attach_(std::remove_reference<std::vector<int> >::type, int&)'
attach_(std::move(vector<int>{1,2,3}),k);
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'T' ('int' and 'int&')
attach_(std::move(vector<int>{1,2,3}),k);
SO has a question for a similar error Error message "deduced conflicting types for parameter 'const T'" about const references.
I also tested a few other cases, some with type conversions. Some work and others don't.
I heard that universal references like T&&
matches everything. Why does it fail here?
A second question is, how to type attach_
to make sure that move semantics work both for xs
and x
for the appropriate input? Ultimately, I'd like to have variant of the following:
for(int i = 0; i < 100; i++)
xs = attach_(xs,values[i])
work without making unnecessary copies.
(This is tested with gcc4.8.1, using g++ -std=c++11 test.cpp)
Thanks
-- EDIT ---
Thanks everyone for the excellent answers.
So I understand now that for this case, it's efficient just to use pass-by-value, and move the T
. It take it that the vector xs is not copied unnecessarily in parameter-passing and returning back, if used in a loop, right?
I asked a related question When is a const reference better than pass-by-value in C++11?. There, I had this example where everyone said pass-by-vale is a bad idea:
int hd(vector<int> a) {
return a[0];
}
Is it possible at all to use universal reference to handle both the hd
case and the attach_
case in this post to avoid unnecessary copies?
Thanks again.
--- EDIT2 ---
So, I tested the versions in the answers plus a const
reference version below. Optimization is not used to expose any potential issues. The const
ref version is the worst as it forces copying. Everything else has the same speed if std::move(a)
is used for the vector, except raw push_call
calls are faster. I guess optimization can remove that difference. I guess the test (or maybe the int type) is not big enough to show the difference between push_back(x)
and push_back(std::move(x))
#include <vector>
#include <iostream>
#include <chrono>
using namespace std;
template <class T>
vector<T> attach(vector<T> v, T x) {
v.push_back(x);
return v;
}
template <typename T>
vector<T> attach1(vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
template <typename T, typename E = typename std::remove_reference<T>::type>
std::vector<E> attach2(std::vector<E> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
template <typename C, typename T> C attach3(C&& xs, T&& x) {
xs.push_back(std::move<T>(x));
return std::forward<C>(xs);
}
template <class T>
vector<T> attach4(const vector<T>& v, T x) {
vector<T> ret = v;
ret.push_back(x);
return std::move(ret);
}
using namespace std::chrono;
int main() {
int N = 100000;
vector<int> a;
auto time = high_resolution_clock::now();
for (int i = 0; i < N; i++) {
//a.push_back(i); //0s
//a = attach(a,i); //15s
//a = attach(std::move(a),i); //0.03s
//a = attach2(std::move(a),i); //0.03s
a = attach3(std::move(a),i); //0.03s
//a = attach4(std::move(a),i); //14.9s
}
cout << duration_cast<duration<double>>(high_resolution_clock::now() - time).count() << endl;
}
The way universal references work is like this: if you pass in an rvalue, then T
will be deduced as int
(or some other non-reference type), because then T&&
is an rvalue reference type. But if you pass in an lvalue, then T
will be deduced as int&
(or some other lvalue reference type), because then T&&
will be an lvalue reference type (since an lvalue reference and an rvalue reference "collapse" together into an lvalue reference).
So in the case that you pass in an lvalue, you have a problem because you can't have vector<T>
when T
is a reference type.
You should just pass by value,
template <typename T>
std::vector<T> attach_(std::vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
This might look less efficient, but it isn't. If you pass in an rvalue, it'll be moved once into x
, and moved once again into the vector. If you pass in an lvalue, it'll be copied once into x
, and them moved into the vector. It's the same as if you passed by reference: one copy for an lvalue, zero copies for an rvalue.
For educational purposes, you could do this with your universal reference:
template <typename T, typename E = typename std::remove_reference<T>::type>
std::vector<E> attach_(std::vector<E> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
This ensures that when you pass an lvalue, the vector element type is the non-reference type. But it really is better to just pass by value.