2013-03-29 64 views
5

我遇到了一个看起来非常出乎意料的重载解析行为。下面的代码被拒绝,并通过GCC和铛多义性错误:使用默认功能模板参数的意外重载分辨率

template <typename T> 
struct A 
{ 
    typedef T key_type; 
}; 

template <typename T> 
void foo(A<T> rng, T val); 

template <typename T, typename U = T> 
void foo(T, typename U::key_type); 

int main() 
{ 
    A<int> i; 
    foo(i, 0); 
} 

的错误是:

test.cpp:16:5: error: call to 'foo' is ambiguous 
    foo(i, 0); 
    ^~~ 
test.cpp:8:6: note: candidate function [with T = int] 
void foo(A<T> rng, T val); 
    ^
test.cpp:11:6: note: candidate function [with T = A<int>, U = A<int>] 
void foo(T, typename U::key_type); 
    ^

我希望既要精确匹配,但第一个重载在偏序取胜,因为在第一个参数A<T>T更专业。

什么最令我嫉妒的是,如果我改变了第二签名:

template <typename T, typename U = T> 
void foo(T, typename T::key_type); 

GCC和铛现在接受的代码,并选择以我原本预计第一批超载。

我不明白这种改变会如何改变行为:我所做的只是使用一个模板参数,它既没有明确指定也没有用默认值(T)推导出来(U)。

然后再次,改变之前的行为是意外的开始,所以也许我失去了一些东西。

有人能解释一下:

  1. 为什么第一种情况是模糊的;和
  2. 为什么我所做的改变解决了歧义?

万一它是相关的,我测试的编译器版本是gcc 4.8.0和最近的躯干构建的铿锵声。

+2

+1,但看起来像一个简单的编译器错误给我。函数模板默认参数是新的。 – Potatoswatter

+0

@Patatoswatter:我怀疑是这样,但是由于gcc和clang具有相同的行为,我想仔细检查一下,在报告它之前,我不知道这是否符合标准。 – HighCommander4

+0

我注意到它们的错误,怪癖和诊断信息有很多重叠......无论法律含义如何,它们不完全是干净的房间设计。 – Potatoswatter

回答

2

问题是在参数推导之后,推导的模板参数是否存在到参数列表中的替换阶段。这个阶段是默认参数将被用于尚未被推导出的模板参数的地方。

对于这个额外步骤完成哪些演绎上下文以及什么不是活动核心问题的主题http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#697的问题。

如果你做了额外的替换步骤,你还需要实例化模板(否则替换步骤本身没有多大意义)。你也可以只选择默认参数,而不进行替换,但在标准中,这两件事合在一起,因此作为实现者我不会选择这种路径。

部分排序在很大程度上与部分排序正在进行的上下文无关(某些与上下文相关的事物被考虑 - 例如,没有显式调用参数的函数参数被忽略)。它也与模板参数是否具有明确的模板参数是否通过有关(因此,如果您给出的值为U,则偏序不会“记住”它。)

Clang和GCC不做所以当TU::key_type比较得出U时,他们会说:“这是一个非推论的上下文,我们会说'成功,没有任何不匹配!'这个参数“。当它比较TT::key_type时会发生同样的情况。当它比较其他方向时,WhatEver::key_typeT,T也可以推断出相关类型。因此,对于第二个参数,在两次尝试中,两个模板都至少与彼此相同。

但是,重要的区别是扣除后,参数类型列表中使用的所有参数都必须有值。

在大多数情况下,所有的模板参数必须具有值,以便扣成功,但对于偏序目的的模板参数可以保持没有值提供被用于偏序它不是在类型使用。 [注意:在未推导的上下文中使用的模板参数被视为使用。 - 注完]

在你的第二次尝试,T由第一个参数推导出,因此参数的比较后不会遭遇不测/参数类型分别进行。在第一次尝试中,U没有被引用,所以第一个模板在后续事件中不被认为是“更专业”的。