如何让模板类型推导与引用一起工作?

8
我有一个模板函数func:
template<typename T>
void func(T p)  { f(p); }

一组函数 f:
f(SomeType&);
f(int);
...

如果我使用引用作为函数参数p来实例化模板函数func,而没有明确指定模板参数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&
  • 有没有一种定义模板函数func的方法,使得类型推导可以使用引用类型,而不需要显式指定模板参数T

明确一点,我希望能够同时使用以下两个函数(参见上面的函数f):

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

5
简而言之,这就是模板参数推导的工作原理。 - 101010
3个回答

12

首先,从一个值初始化对象和将引用绑定到一个值被认为是同样好的(在重载决议中都是“完全匹配”),因此参数的引用性不会被推断。相反,您必须精确指定它。关于相关的例子,请考虑一个假设的构造。

T x = f();

在这个思想实验中,T 是你被要求推断的东西。那么 x 应该是一个对象还是一个引用?这将取决于什么?右侧的表达式肯定有一个值,而该值的类型始终是一个对象类型。你应该如何决定?你可以查询表达式的值类别,但这太微妙了。所以,必须说出你想要什么:

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

在C++中,如果你用auto替换T,实际上这个机制是可用的。对于函数模板的模板参数推导,同样适用相同的规则。
现在来看如何解决你的问题。通常的惯用法是让函数模板始终使用引用,并将参数转发到内部调用:
template <typename T>
void func(T && t)
{
    f(std::forward<T>(t));
};

现在,在任何特化情况下,func的参数始终是一个引用(但由于一种奇怪的规则称为“引用折叠”,它可以是左值引用或右值引用),并且该值以相同的值类别组转发到f,并且当func的原始参数是左值时,此重载解析允许选择左值引用重载。请保留HTML标记。

1
通用引用和引用折叠现在对我来说更加清晰了,非常感谢您详细的回答。 - shrike
6
“没有这样的东西。”函数调用表达式是一个表达式,并且表达式总是具有对象类型。函数返回类型上的引用资格确定表达式的值类别。函数不能“返回一个引用”,那只是一种粗心的口语用法。 - Kerrek SB
@KerrekSB 我不明白三个“是”的意思。我只提出了两个需要确认的观点。或者第一个“是”是一个总体的“是”吗? - Johannes Schaub - litb
3
抱歉,我当时是在说反话。我的意思是“我知道,但不想详细解释”。我不想用其他类型的表达式来复杂化我的观点,并希望这个教训是表达式从来不是引用。当然,你是对的,一个表达式可以有几种类型。 - Kerrek SB
1
@KerrekSB 不用担心,哈哈。我只是开玩笑 :p - Johannes Schaub - litb
显示剩余2条评论

7

使用转发引用:

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

现在请在该网站上搜索“转发引用”(或以前称为通用引用)以及std::forward,以了解更多相关信息。


0

考虑以下示例:

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&)。这完全违背了程序员的意图。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接