推导参数'T'的冲突类型以供通用参考



我正在用以下代码测试通用引用,

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 T'&;关于const引用。

我还测试了其他一些情况,其中一些使用类型转换。

我听说像T&&这样的通用引用匹配一切。为什么它在这里失败了?

第二个问题是,如何输入attach_以确保移动语义对xsx都有效,以获得适当的输入?最后,我希望有以下内容的变体:

for(int i = 0; i < 100; i++)
   xs = attach_(xs,values[i])

可以工作而不需要复制。

(使用gcc4.8.1测试,使用g++ -std=c++11 test.cpp)

感谢

—EDIT—

感谢大家的精彩回答。

所以我现在明白了,对于这种情况,使用按值传递并移动T是有效的。如果在循环中使用,在传递参数和返回参数的过程中,不会不必要地复制向量xs,对吧?

我问了一个相关的问题:在c++ 11中,什么时候const引用比按值传递更好?在这里,我有一个例子,每个人都说传递值是一个坏主意:

int hd(vector<int> a) {
   return a[0];
}
在这篇文章中,是否有可能使用通用参考来处理hdattach_的情况,以避免不必要的复制?

再次感谢。

——

所以,我测试了答案中的版本以及下面的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;
}

通用引用的工作方式是这样的:如果你传递一个右值,那么T将被推断为int(或其他一些非引用类型),因为T&&是一个右值引用类型。但是如果你传递一个左值,那么T将被推断为int&(或其他左值引用类型),因为T&&将是一个左值引用类型(因为左值引用和右值引用"折叠"在一起成为一个左值引用)。

所以在传递左值的情况下,你有一个问题,因为当T是引用类型时,你不能有vector<T>

你应该按值传递,

template <typename T>
std::vector<T> attach_(std::vector<T> xs, T x) {
  xs.push_back(std::move(x));
  return xs;
}

这可能看起来效率较低,但事实并非如此。如果传入一个右值,它将被移动一次到x中,并再次移动到vector中。如果你传入一个左值,它将被复制一次到x中,并将它们移动到向量中。这与通过引用传递相同:左值复制一次,右值复制零次。

出于教育目的,您可以使用通用引用:

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;
}

确保在传递左值时,vector元素类型是非引用类型。但是最好还是按值传递

我听说像T&&比赛一切。为什么它在这里失败了?

通称引用匹配一切的原因是因为模板参数演绎规则规定,当T &&与类型为X的左值配对时,T被演绎为X&,然后引用崩溃使X& &&变成X&

attach_(std::move(vector<int>{1,2,3}),k);中,编译器从第一个参数(vector<T> <-> vector<int>)推导出Tint,从第二个参数T推导出int &,因为k为左值。因此,您将得到一个错误。

第二个问题是,如何输入attach_以确保移动语义对xsx都有效,以获得适当的输入?

最简单的方法是按值传递并将其移动到vector中:

template <typename T>
vector<T> attach_(vector<T> xs, T x) {
  xs.push_back(std::move(x));
  return xs;
}

如果传递std::move, x仍然会被移动构造。


编辑:如果您正在使用一个大型的仅复制的遗留类型,那么在上述情况下创建两个副本并不理想。在这种情况下,你可以做@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:

所以我现在明白了,对于这种情况,使用按值传递并移动T是有效的。如果在循环中使用,那么在传递参数和返回参数时不会不必要地复制向量xs,对吗?

一般规则是"如果它很小或者无论如何都需要复制,则按值传递,否则按引用传递"。在attach_中,您需要复制x(通过push_back将其放入向量中),因此通过值传递它,然后移动它是可以的。

是否应该按值传递vector取决于您想要的语义。如果attach_(xs, x) 不应该改变xs,那么您将需要复制要返回的向量,因此您应该按值传递它。但是,当您执行xs = attach_(xs, x);时,将产生一个副本。xs = attach_(std::move(xs), x);不会产生拷贝,但是由于move构造和move赋值有一些额外的开销。

如果attach_(xs, x) 应该改变xs,那么通过非const引用传递它。不涉及开销。

在这篇文章中,是否有可能使用通用参考来处理hdattach_的情况,以避免不必要的复制?

hd不需要通用引用。你只是索引一个向量,所以通过const引用传递它。

通用引用语义根据以下引用工作:

8.3.2参考文献§6

如果typedefine -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));
}

最新更新