功能模板和非推断上下文的部分排序在MSVC 2017中不起作用

时间:2017-08-19 09:33:23

标签: c++ c++11 templates

identity模板是关闭某个(依赖)模板参数的演绎的常用习惯用法,例如允许隐式转换,如下例所示:

#include <iostream>

template<class T>
struct A {};
struct B : public A<int> {};
struct C {
  operator B() { return {}; }
};

template<typename U> struct identity { typedef U type; };

template<class T> using identity_t = typename identity<T>::type;

template<class X>
void test(A<X> arg1, A<X> arg2) {              // #1
  std::cout << "ok1";
}

template<class X>
void test(A<X> arg1, identity_t<A<X>> arg2) {  // #2
  std::cout << "ok2";
}

int main() {
  B a, b;
  C c;
  test(a, b);
  test(b, c);
}

但不同编译器的结果并不相同:Live demo on Godbolt

  • GCC 6:ok1ok2
  • clang 5:ok1ok2
  • MSVC 2017:

27 : <source>(27): error C2668: 'test': ambiguous call to overloaded function
20 : <source>(20): note: could be 'void test<int>(A<int>,A<int>)'
15 : <source>(15): note: or       'void test<int>(A<int>,A<int>)'
27 : <source>(27): note: while trying to match the argument list '(B, B)'

错误类型有意义(虽然肯定是MSVC中的一个错误),因此带给我们关于为什么以及它如何在GCC和clang中起作用的问题

  1. test(a, b) - #1test(b, c)如何选择#2?至少在test(a, b) {。}}

  2. 的情况下,他们看起来都是同样出色的候选人
  3. 为什么编译器抱怨两个test实例具有完全相同的签名?

1 个答案:

答案 0 :(得分:1)

这是MSVC中的一个错误,程序是正确的。

  

test(a, b)如何选择#1

对于test(a, b),重载决策从函数调用中执行参数推导(参见[temp.deduct.call]):

  • #1推断为void test(A<int>, A<int>)
  • #2推断为void test(A<int>, <non-deduced context>),然后arg2从arg1合成为A<int>,结果为:void test(A<int>, A<int>)

有多个可行的替代方案,因此该过程将继续部分排序(请参阅[temp.deduct.partial])。

部分排序使用原始模板,尝试从类型([temp.deduct.type])中的扣除,将一个模板的每个参数成对地转换为另一个模板(之后)小转换),反之亦然。 如果扣除仅在一个方向上成功,则选择获胜模板作为最专业的。

在嵌套的上下文中,类型的扣除总是失败(范围操作符::左侧的任何内容都是嵌套的上下文),请参阅[temp.deduct.type]/5

  

未推断的上下文是:

     

- 使用 nested-name-specifier 指定的类型的 qualified-id

     

。 。

所以这意味着#2总会在部分排序中丢失;对它的推论总是会失败,而另一种方式总是会成功:

  1. void test(A<T>,A<T>)推导void test(A<U>, typename identity<A<U>>::type):P1 = A<T>,A1 = A<U>,P2 = A<U>,A2 = A<U> ,成功,T = U

  2. void test(A<T>, typename identity<A<T>>::type)推导void test(A<U>,A<U>):P1 = A<T>,A1 = A<U>,P2 = <non-deduced-context>,失败

  3. 因此,部分排序的结果是:使用void test(A<T>,A<T>)(#1)进行调用test(a, b)

      

    test(b, c)如何选择#2

    对于test(b, c),无法从A<X>推断出C(在演绎时不考虑隐式转换),因此#2是唯一可行的替代方案。在扣除identity_t<A<X>>之后解析,因为A<int>已知(从第一个参数推断出来)。

      

    为什么编译器不抱怨具有完全相同签名的两个测试实例?

    函数声明中引用的模板参数是实例化函数的签名的一部分。见[temp.over.link]

      
        
    1. 可以重载函数模板,以便两个不同的函数模板特化具有相同的类型。

    2.   
    3. 此类专业化是不同的功能,不违反单一定义规则。

    4.