【问题标题】:deduced conflicting types for parameter 'T' for universal reference推导出参数“T”的冲突类型以供通用参考
【发布时间】:2014-08-28 14:32:53
【问题描述】:

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

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&amp;&amp; 这样的通用引用可以匹配所有内容。为什么这里会失败?

第二个问题是,如何键入 attach_ 以确保移动语义适用于 xsx 以获取适当的输入?最终,我想要以下变体:

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

工作时不要制作不必要的副本。

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

谢谢

-- 编辑 ---

感谢大家的精彩回答。

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

我问了一个相关问题When is a const reference better than pass-by-value in C++11?。在那里,我有一个例子,每个人都说 pass-by-vale 是个坏主意:

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;

}

【问题讨论】:

  • 那里没有通用参考。 UR 必须只有一个函数参数。
  • 哦,我明白了。所以不仅我们不能在 T&& 中添加 const 等,而且我们不能有任何其他类型涉及 T?
  • 那会彻底失败,不是吗...
  • 我还在学习这个。观看了 Scott 的一段视频,大约有两个 & 符号。为什么人们不使用 ` 或其他东西作为正确的引用,而是想出所有这些规则和特殊情况?

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


【解决方案1】:

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

所以在你传入左值的情况下,你会遇到问题,因为当T 是引用类型时,你不能拥有vector&lt;T&gt;

你应该只是按值传递,

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

这可能看起来效率较低,但事实并非如此。如果您传入一个右值,它将被移动一次到x,然后再次移动到向量中。如果你传入一个左值,它会被复制一次到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;
}

这确保当你传递一个左值时,向量元素类型是非引用类型。但最好只传递值。

【讨论】:

  • 如果我们走第二条路线,那么 Deduplicator 的解决方案会更好。在这种情况下,参数E 将始终从向量的类型推导出来,而不是使用默认参数。
  • @T.C.是的,这可能是一个更好的解决方案。
【解决方案2】:

我听说像 T&& 这样的通用引用可以匹配所有内容。为什么这里会失败?

通用引用匹配一切的原因是因为模板参数推导规则说当T &amp;&amp;X类型的左值配对时,T被推导出为X&amp;,然后引用折叠使得X&amp; &amp;&amp;变成X&amp;

attach_(std::move(vector&lt;int&gt;{1,2,3}),k);中,编译器从第一个参数(vector&lt;T&gt; vector&lt;int&gt;)推导出Tint,而从第二个参数推导出T为int &amp;,因为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:

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

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

是否应该按值传递向量取决于您的预期语义。如果attach_(xs, x) 不应该改变xs,那么您将需要制作一个向量的副本以返回,因此您应该按值传递它。但是,当您执行xs = attach_(xs, x); 时,您将获得一份副本。 xs = attach_(std::move(xs), x); 不会产生副本,但在移动构造之后会产生一些额外的开销,然后是移动分配。

如果attach_(xs, x) 应该改变xs,然后通过非常量引用传递它。不涉及任何开销。

是否有可能使用通用引用来处理本文中的hdattach_ 情况以避免不必要的复制?

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

【讨论】:

    【解决方案3】:

    通用引用语义根据此引用工作:

    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&
    

    ——结束示例]

    明智地使用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));
    }
    

    不再需要返回容器...

    【讨论】:

    • 两个模板参数绝对是这里的方式,IMO。但是,您的最后一个示例返回类型错误。
    • 最后一个示例还需要对他当前使用函数的方式进行重大更改(他将右值作为第一个参数)
    • @MattMcNabb:是的,它有不同的签名。这就是最后一节的重点,给它一个更有意义的签名,这可能意味着它的使用方式发生了变化。
    猜你喜欢
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 2021-03-05
    • 1970-01-01
    • 2020-05-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多