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

HC4*_*ica 5 c++ function-templates overload-resolution c++11

我遇到了一个看起来很意外的重载解析行为.以下代码被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);
}
Run Code Online (Sandbox Code Playgroud)

错误是:

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);
     ^
Run Code Online (Sandbox Code Playgroud)

我希望两者都是完全匹配,但是第一次超载是在部分排序中获胜,因为在第一个参数A<T>中比较专业T.

让我感到震惊的是,如果我将第二个签名更改为:

template <typename T, typename U = T>
void foo(T, typename T::key_type);
Run Code Online (Sandbox Code Playgroud)

gcc和clang现在都接受代码,并按照我原先的预期选择第一个重载.

我没有看到这种变化如何在行为上产生影响:我所做的只是替代使用模板参数,既没有明确指定也没有推导(U)及其默认值(T).

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

有人能解释一下:

  1. 为什么第一个案例含糊不清; 和
  2. 为什么我所做的改变解决了歧义?

如果它是相关的,我测试的编译器版本是gcc 4.8.0和最近的clang的trunk构建.

Joh*_*itb 2

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

\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n

但重要的区别在于,推导后,参数类型列表中使用的所有参数都必须有值。

\n\n
\n

在大多数情况下,所有模板参数都必须具有值才能成功推导,但出于部分排序的目的,只要模板参数未在用于部分排序的类型中使用,模板参数就可以保持没有值。[ 注意:在非推导上下文中使用的模板参数被视为已使用。\xe2\x80\x94 尾注]

\n
\n\n

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

\n