Partial ordering of function templates and non-output context not working in MSVC 2017

The identity template is a common idiom for disabling the deduction for a specific (dependent) argument of a template, for example, to allow implicit conversions, as in the example below:

 #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); } 

But the results from different compilers do not match: 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)' 

The error makes sense (although it is certainly an error in MSVC), and therefore leads us to my questions about why and how it works in GCC and clang :

  • How to test(a, b) to select #1 , and test(b, c) to #2 ? They look equally good candidates even in the case of test(a, b) .

  • Why doesn't the compiler complain about two test instances with the same signature?

+3
c ++ c ++ 11 templates
source share
1 answer

This is an error in MSVC, the program is correct.

How to test(a, b) choose #1 ?

For test(a, b) , overload resolution performs the output of the argument from the function call (see [temp.deduct.call] ):

  • # 1 is output as void test(A<int>, A<int>)
  • # 2 is output as void test(A<int>, <non-deduced context>) , arg2 is then synthesized from arg1 as A<int> , the result is: void test(A<int>, A<int>)

There is more than one viable alternative, so the process continues with partial ordering (see [temp.deduct.partial] ).

Partial ordering uses the original templates, trying to subtract from the type ( [temp.deduct.type] ) a pair from each argument of one template to another (after a slight conversion) and vice versa. If the deduction succeeds in only one direction, the winning template is selected as the most specialized.

Type inference is not always performed in nested contexts (anything to the left of the scope :: operator is a nested context), see [temp. deduct.type] / 5 :

Impossible contexts:

- nested-name-specifier of the type that was specified using qualified-id .

.,.

So, this means that # 2 will always lose in partial order; the output to it will always fail, while the other path will always be successful:

  • Output of void test(A<T>,A<T>) from void test(A<U>, typename identity<A<U>>::type) : P1 = A<T> , A1 = A<U> , P2 = A<U> , A2 = A<U> , success, T = U

  • The output of void test(A<T>, typename identity<A<T>>::type) from void test(A<U>,A<U>) : P1 = A<T> , A1 = A<U> , P2 = <non-deduced-context> , fail

So, the result of partial ordering is to use void test(A<T>,A<T>) (# 1) to call test(a, b) .

How to test(b, c) choose #2 ?

For test(b, c) , A<X> cannot be inferred from C (implicit conversions are not taken into account when subtracting), so # 2 is the only viable alternative. identity_t<A<X>> allowed after deduction to A<int> , since X known (derived from the first argument).

Why doesn't the compiler complain about two test instances with the same signature?

The template parameters specified in the function declaration are part of the signature of the created function. See [temp.over.link] :

  • You can overload function templates so that two different specialized function templates have the same type.

  • Such specializations represent various functions and do not violate one definition rule.

+1
source share

All Articles