【问题标题】:How do I make template type deduction work with references?如何使模板类型推导与引用一起使用?
【发布时间】:2016-05-06 09:33:52
【问题描述】:

我有一个模板函数,func:

template<typename T>
void func(T p)  { f(p); }

还有一组函数f

f(SomeType&);
f(int);
...

如果我实例化模板函数func,使用引用作为函数参数p,而没有明确指定模板参数T,那么推导的类型将不是p的引用类型,而是p 类型是一个引用,例如:

SomeType s;
SomeType& ref_to_s = s;
func(ref_to_s);             // Type deduction results in: func<SomeType>(ref_to_s)
func<SomeType&>(ref_to_s);  // Need to explicitly specify type to work with reference

所以,我的问题是:

  • 为什么在上述情况下编译器无法推断出引用类型SomeType&amp;
  • 有没有办法定义模板函数func,以便类型推导适用于引用类型,而无需显式指定模板参数T

需要明确的是,我想要两者都可以使用的东西(参见上面的函数f):

func(ref_to_s);    // Calls func<SomeType&>(ref_to_s)
func(1);           // Calls func<int>(1)

【问题讨论】:

  • 很明显,这就是模板参数推导的工作原理。

标签: c++ templates c++11 c++14


【解决方案1】:

第一部分的答案是,从一个值初始化一个对象和将一个引用绑定到一个值被认为是同样好的(在重载决议中两者都是“精确匹配”),因此不会推断出参数的引用性.相反,您必须准确地指定它。对于一个相关的例子,考虑一个假设的结构

T x = f();

T 是(在这个思想实验期间)你应该推断的东西。 x 应该是一个对象还是一个引用?这将取决于什么?右边的表达式当然有一个值,而那个值的类型总是对象类型。你应该如何决定?您可以查询表达式的值类别,但这太微妙了。因此,必须说出你想要的:

T x = f();    // x is an object
T & r = f();  // r is a reference

在 C++ 中,如果将 T 替换为 auto,则该机制实际上是可用的。相同的规则适用于函数模板的模板参数推导。

现在谈谈如何解决您的问题。通常的习惯用法是让函数模板always取一个引用,然后将参数转发给内部调用:

template <typename T>
void func(T && t)
{
    f(std::forward<T>(t));
};

现在func 的参数在任何特化中始终是一个引用(但由于称为“引用折叠”的奇怪规则,它可以是左值或右值引用),并且该值被转发 f 相同的值类别组,并且f 上的重载决议允许在func 的原始参数是左值时选择左值引用重载。

【讨论】:

  • 通用参考和参考折叠现在对我来说更清楚了;非常感谢您的详细回答。
  • "右边的表达式当然有值,而那个值的类型总是对象类型。"如果 f() 返回一个引用呢?
  • @bipll:没有这样的事情。函数调用表达式是一个表达式,表达式总是有对象类型。函数返回类型的引用限定决定了表达式的值类别。函数不能“返回引用”,这只是一种草率的口语。
  • @JohannesSchaub-litb:对不起,我在开玩笑。它的意思是“我知道,但不想详细说明”。我不想用其他类型的表达使我的观点复杂化,我希望得到的教训是表达永远不是引用。当然,表达式可以有多种类型是对的。
  • @KerrekSB 别担心,哈哈。我只是在开玩笑:p
【解决方案2】:

使用转发引用:

template<typename T>
void func(T && p)    //notice &&
{ 
    f(std::forward<T>(p));  //notice std::forward
}

现在请在本网站上搜索 forwarding-reference(或前面提到的 universal-reference)以及 std::forward 以了解更多信息。

【讨论】:

    【解决方案3】:

    考虑以下示例:

    template<typename T>
    void f(T value, T& ref) {// fake swapping
    T temp = ref;
    ref = value;
    value = temp;
    }
    

    当编译器试图推断T的类型时,合理的做法是首先从“value”和“ref”中删除可能的引用,以便

    int i = 1; 
    int j = 2;
    f(i, j);    //value of j becomes 1.
    //up to here, value of i is still 1
    

    否则,事情会变得很奇怪。如果以其他方式将 T 推导出为 T&,则 f(i, j) 的类型将与 f(int&, int& &) 等价。假设 T& & 折叠成 T&,那么它就变成了 f(int&, int&)。这完全违背了程序员的意图。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-22
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 1970-01-01
      相关资源
      最近更新 更多