我正在使用以下代码测试通用引用,
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
}
并收到错误:
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有关于const引用的类似错误Error message "deduced conflicting types for parameter 'const T'"的问题。
我还测试了一些其他案例,其中一些案例有类型转换。一些工作和其他工作没有。
我听说像T&&
这样的通用引用符合所有内容。为什么在这里失败?
第二个问题是,如何键入attach_
以确保移动语义对xs
和x
的适当输入都有效?最后,我想要有以下变体:
for(int i = 0; i < 100; i++)
xs = attach_(xs,values[i])
无需制作不必要的副本即可完成工作。
(这是用gcc4.8.1测试的,使用g ++ -std = c ++ 11 test.cpp)
由于
- 编辑---
感谢大家的出色答案。
所以我现在明白,对于这种情况,只使用pass-by-value并移动T
是有效的。它认为向量xs不会在参数传递中被不必要地复制并返回,如果在循环中使用,对吧?
我问了一个相关的问题When is a const reference better than pass-by-value in C++11?。在那里,我有一个例子,每个人都说过传递是一个坏主意:
int hd(vector<int> a) {
return a[0];
}
是否可以使用通用引用来处理本文中的hd
案例和attach_
案例,以避免不必要的副本?
再次感谢。
--- EDIT2 ---
所以,我测试了答案中的版本以及下面的const
参考版本。优化不用于揭示任何潜在问题。 const
ref版本是最糟糕的,因为它强制复制。如果std::move(a)
用于向量,则其他所有内容都具有相同的速度,但原始push_call
调用更快。我想优化可以消除这种差异。我猜测试(或者可能是int类型)不足以显示push_back(x)
和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;
}
答案 0 :(得分:7)
通用引用的工作方式如下:如果传入右值,则T
将推导为int
(或其他一些非引用类型),因为{{1}是一个右值引用类型。但是如果你传入左值,那么T&&
将被推导为T
(或其他一些左值引用类型),因为int&
将是左值引用类型(因为左值引用)和右值参考&#34;一起折叠&#34;一起进入左值参考。)
因此,如果您传入左值,则会遇到问题,因为当T&&
是参考类型时,您无法vector<T>
。
你应该按值传递,
T
这可能看起来效率较低,但并非如此。如果你传入右值,它会被移动到template <typename T>
std::vector<T> attach_(std::vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
一次,然后再次移动到向量中。如果您传入左值,则会将其复制一次到x
,然后将它们移入向量中。它与通过引用传递的方式相同:左值为一个副本,右值为零副本。
出于教育目的,您可以使用通用参考:
x
这可确保在传递左值时,矢量元素类型为非引用类型。但是,通过价值传递真的更好。
答案 1 :(得分:4)
我听说T&amp;&amp; amp;匹配一切。为什么在这里失败?
通用引用匹配所有内容的原因是因为模板参数推导规则表明当T &&
与类型X
的左值配对时,T被推断为X&
,然后引用折叠会使X& &&
变为X&
。
在attach_(std::move(vector<int>{1,2,3}),k);
中,编译器会从第一个参数(T
&lt; - &gt; int
)中将vector<T>
推断为vector<int>
,而在第二个参数中参数T推导为int &
,因为k
是左值。因此,您会收到错误。
第二个问题是,如何输入
attach_
以确保移动语义对xs
和x
的适当输入都有效?
最简单的方法是简单地按值传递它并将其移动到向量中:
template <typename T>
vector<T> attach_(vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
x
如果与std::move
一起传递,仍然会被移动构建。
编辑:如果您正在使用大型仅复制旧版本,那么在上述情况下制作两份副本并不理想。在这种情况下,你可以做@Deduplicator在他的回答中显示的内容:
template <typename T>
vector<typename std::remove_reference<T>::type>
attach_(vector<typename std::remove_reference<T>::type> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
编辑2:
所以我现在明白,对于这种情况,只使用pass-by-value并移动
T
是有效的。它认为向量xs
不会在参数传递中被不必要地复制并返回,如果在循环中使用,对吧?
一般规则是“如果它很小或者无论如何你需要复制,那么通过值传递,否则通过引用”。在attach_
中,您需要制作x
的副本(通过push_back
将其传递到向量中),因此按值传递它然后移动它是精细。
是否应该按值传递向量取决于您的预期语义。如果attach_(xs, x)
不应该改变xs
,那么你需要制作一个向量的副本以便返回,因此你应该通过它按价值。但是,当您执行xs = attach_(xs, x);
时,您将获得一份副本。 xs = attach_(std::move(xs), x);
不会产生副本,但移动分配后的移动构造会产生一些额外的小额开销。
如果attach_(xs, x)
应更改xs
,则通过非const引用传递它。不涉及任何开销。
是否可以使用通用引用来处理本文中的
hd
案例和attach_
案例,以避免不必要的副本?
hd
不需要普遍参考。您只是索引一个向量,所以只需通过const引用传递它。
答案 2 :(得分:2)
通用引用语义根据此引用工作:
8.3.2参考文献§6
如果typedef-name(7.1.3,14.1)或decltype-specifier(7.1.6.2)表示类型TR是对类型T的引用,则尝试创建类型“对cv TR的左值引用” “创建类型”对“T”的左值引用,而尝试创建类型“对cv TR的右值引用”则创建类型TR。 [例如:
int i; typedef int& LRI; typedef int&& RRI; LRI& r1 = i; // r1 has the type int& const LRI& r2 = i; // r2 has the type int& const LRI&& r3 = i; // r3 has the type int& RRI& r4 = i; // r4 has the type int& RRI&& r5 = 5; // r5 has the type int&& decltype(r2)& r6 = i; // r6 has the type int& decltype(r2)&& r7 = i; // r7 has the type int&
-end example]
明智地使用std::remove_reference
可以轻松解决您的错误:
#include <vector>
using namespace std;
template <typename T>
vector<typename std::remove_reference<T>::type>
attach_(vector<typename std::remove_reference<T>::type> 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); //now OK
attach_(std::move(vector<int>{1,2,3}),(int&)k); //now OK
attach_(std::move(vector<int>{1,2,3}),(int)k); //OK
attach_(std::move(vector<int>{1,2,3}),2); //OK
}
无论如何,您是否确定要通过值传递容器?
另外,为什么不给它自己的模板参数来使函数更通用呢?
template <typename T, typename C> void attach_(C& xs, T&& x) {
xs.push_back(std::forward<T>(x));
}
不再需要退回容器......