为什么在推导类型时模板参数的限定符会被去除?

8

在使用Microsoft VisualStudio 2008构建一个小的示例程序时,我注意到有关传递给模板的类型推断的奇怪事情。考虑以下示例:

template<class T>
void f( T v ) {
    x; // trigger a compile error
    (void)v;
}

template<class T>
void g( T v ) {
    f( v );
}

void h() {
  int i;
  g<const int &>( i );
}

使用cl /c foo.cpp编译此示例会导致编译错误(这是预期的)。有趣的是'T'模板参数的值。以下是VisualStudio 2008打印的内容:

mini.cpp(3) : error C2065: 'x' : undeclared identifier
        mini.cpp(9) : see reference to function template instantiation 'void f<int>(T)' being compiled
        with
        [
            T=int
        ]
        mini.cpp(14) : see reference to function template instantiation 'void g<const int&>(T)' being compiled
        with
        [
            T=const int &
        ]

请注意,在函数 g 中,参数的类型是 const int &,但在函数 f 中,它只是一个普通的 int 类型。显然,在推断实例化 f 模板时使用的类型时,对于引用到常量的部分进行了剥离。当调整示例以使 f 被调用时,请注意这一点。
f<T>( v );

fg中的类型都是const int &。为什么会这样?这是指定的行为吗?我曾经暗自依赖于传递给fv函数参数的类型,但显然不是。


C++模板 + MSVC++ = 不良组合。 - Prasoon Saurav
2
@Prasoon:GCC 推断出相同的类型。当然,在 GCC 中,提问者的代码会在 f 模板中触发编译错误,因为 GCC 正确地进行了两阶段编译。x 不依赖于模板参数,所以应该在第一阶段被拒绝(如 GCC 中),而不是在第二阶段(如 MSVC 中)。但是将 x 更改为 v = 1;,很容易看出,除非你在 g 中显式指定 f<T>(v),否则 GCC 不会使用 const int& 实例化 f - Steve Jessop
2个回答

7
答案是,尽管变量 v 的类型为 const int &,但 表达式 v 是一个带有类型 const int 的左值表达式。 litb 提供的文本(5/6):如果一个表达式最初具有类型“引用 T”(8.3.2、8.5.3),则在进一步分析之前将类型调整为“T”,该表达式指定由引用所表示的对象或函数,并且该表达式是左值。
“参数”是“函数调用表达式中由括号限定的逗号分隔列表中的表达式”(1.3.1)。因此,在14.8.2.1中:
  • “调用的对应参数类型(称其为 A)”为 const int
  • “如果 A 是 cv 限定类型,则忽略 A 类型的顶层 cv 限定符以进行类型推断”(因此,int)。
  • “推断过程尝试找到模板参数值,使得推导出的 A 与 A 相同”(因此 T 为 int

你可能是对的。A和P的标准术语让我完全困惑了 =| - icecrime
@icecrime:我也是,这不是我非常熟悉的标准的一部分。我认为对“P”的调整意味着如果f被声明为template<typename T> void f(const T v)或者template<typename T> void f(T &v),那么T将用于类型推导。但我不确定我是否正确理解了它。 - Steve Jessop
即使这不是100%正确的,它也足够接近了。如果我们看表达式v+v,显然每个人都会期望它的行为像整数加法一样。在这种情况下,将int&int区别对待是没有意义的。 - MSalters
@GMan的第一粉丝(曾用名Johannes,曾用名litb):不错,谢谢。 - Steve Jessop

1

http://accu.org/index.php/journals/409 是一篇相当详细的文章,但它解释了这个过程。从模板参数中,推导出一个参数类型P,并将其匹配到参数类型A。相关部分是描述如何从函数参数派生出目标类型A:对于非数组类型,引用被简单地去掉。因此,如果参数的类型是int&,那么目标类型A就是int

这是一个简单的原因:因为标准告诉我们如此。这个理由是什么?正好,这篇文章还有一个脚注也指出了这一点。在你的例子中,typeid(v)==typeid(const int)。在非左值上下文中使用时,引用类型的行为类似于非引用类型。


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