5

我遇到了一种看起来非常意外的重载解决行为。以下代码被 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默认值(T)。

再说一次,改变之前的行为是出乎意料的,所以也许我错过了一些东西。

有人可以解释一下:

  1. 为什么第一种情况不明确;和
  2. 为什么我所做的更改解决了歧义?

如果相关,我测试的编译器版本是 gcc 4.8.0 和最近的 clang 主干版本。

4

1 回答 1

2

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

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

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

部分排序在很大程度上独立于正在执行部分排序的上下文(考虑了一些依赖于上下文的事情 - 例如,忽略没有显式调用参数的函数参数)。它也独立于模板参数是否传递了显式模板参数(因此,如果您给 一个值U,部分排序将不会“记住”它。

Clang 和 GCC 不执行替换步骤,也不使用模板默认参数。因此,当与T进行比较U::key_type以弄清楚时U,他们会说“嗯,一个非推断的上下文。我们会说'成功,没有什么不匹配的!'” 对于这个参数”。T与 比较时也会发生同样的情况T::key_type。当它比较另一个方向时,WhatEver::key_type反对TT也可以推断出依赖类型。因此,对于第二个参数,在您的两次尝试中,两个模板至少彼此一样专业。

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

在大多数情况下,所有模板参数都必须具有值才能成功进行推导,但出于偏序目的,模板参数可能会保持没有值,前提是它未用于偏序的类型。[ 注意:在非推导上下文中使用的模板参数被视为已使用。——尾注]

在您的第二次尝试中,T由第一个参数推断,因此在参数/参数类型的比较完成后没有发生任何不好的事情。在第一次尝试中,U没有推导出来,因此第一个模板不被认为比第二个模板“更专业”。

于 2013-03-29T11:08:46.913 回答