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

时间:2014-07-08 00:24:40

标签: c++ c++11 type-deduction

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

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_以确保移动语义对xsx的适当输入都有效?最后,我想要有以下变体:

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;

}

3 个答案:

答案 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_以确保移动语义对xsx的适当输入都有效?

最简单的方法是简单地按值传递它并将其移动到向量中:

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

不再需要退回容器......