使用默认函数模板参数的意外重载解析

时间:2013-03-29 03:55:22

标签: c++ c++11 overload-resolution function-templates

我遇到了一个看起来很意外的重载解析行为。以下代码被gcc和clang拒绝,并出现歧义错误:

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和clang现在都接受代码,并按照我原先的预期选择第一个重载。

我没有看到这种变化如何影响行为:我所做的只是替代使用模板参数,该模板参数既未明确指定也未推断(U)及其默认值({{ 1}})。

然后,改变之前的行为开始意外,所以也许我错过了一些东西。

有人可以解释一下:

  1. 为什么第一个案例含糊不清;和
  2. 为什么我所做的改变解决了歧义?
  3. 如果它是相关的,我测试的编译器版本是gcc 4.8.0和最近的clang树干版本。

1 个答案:

答案 0 :(得分: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未被保留,因此第一个模板在后果中被认为不是“更专业”。