gcc中的模糊重载,msvc没问题

Ambiguous overload in gcc, fine with msvc

本文关键字:msvc 没问题 重载 模糊 gcc      更新时间:2023-10-16

下面的代码在msvc 18.00下可以正常编译,但在gcc 4.9.1下不能正常编译:

#include <type_traits>
template <int N> class Num { };
class Zero { };
template <int N, int M>
Num<N + M> operator+(Num<N>, Num<M>)
{
  return {};
}
template <int N>
Zero operator+(Num<N>, Num<-N>)
{
  return {};
}
int main()
{
  Num<1> one;
  Num<-1> mone;
  Num<0> null;
  auto a = one + one;
  static_assert(std::is_same<decltype(a), Num<2>>::value, ":(");
  auto b = one + mone;
  static_assert(std::is_same<decltype(b), Zero>::value, ":(");
  auto c = null + null;
  static_assert(std::is_same<decltype(c), Zero>::value, ":(");
}

gcc的错误信息是:

ambiguous.cpp: In function 'int main()':
ambiguous.cpp:28:16: error: ambiguous overload for 'operator+' (operand types are 'Num<1>' and 'Num<-1>')
   auto b = one + mone;
                ^
ambiguous.cpp:28:16: note: candidates are:
ambiguous.cpp:8:12: note: Num<(N + M)> operator+(Num<N>, Num<M>) [with int N = 1; int M = -1]
 Num<N + M> operator+(Num<N>, Num<M>)
            ^
ambiguous.cpp:14:6: note: Zero operator+(Num<N>, Num<(- N)>) [with int N = 1]
 Zero operator+(Num<N>, Num<-N>)
      ^
ambiguous.cpp:29:47: error: template argument 1 is invalid
   static_assert(std::is_same<decltype(b), Zero>::value, ":(");
                                               ^
ambiguous.cpp:31:17: error: ambiguous overload for 'operator+' (operand types are 'Num<0>' and 'Num<0>')
   auto c = null + null;
                 ^
ambiguous.cpp:31:17: note: candidates are:
ambiguous.cpp:8:12: note: Num<(N + M)> operator+(Num<N>, Num<M>) [with int N = 0; int M = 0]
 Num<N + M> operator+(Num<N>, Num<M>)
            ^
ambiguous.cpp:14:6: note: Zero operator+(Num<N>, Num<(- N)>) [with int N = 0]
 Zero operator+(Num<N>, Num<-N>)
      ^
ambiguous.cpp:32:47: error: template argument 1 is invalid
   static_assert(std::is_same<decltype(c), Zero>::value, ":(");
                                               ^

哪个编译器是正确的?

我讨厌这么说,但是MSVC是正确的,gcc 5.1和clang 3.6是错误的!为了简化,我们调用:

operator+(Num<1>, Num<-1>)
与过载:

operator+(Num<N>, Num<M>)
operator+(Num<N>, Num<-N>)

两者显然都是可行的候选人。并且,根据[over.match.best]:

给定这些定义,一个可行函数F1被定义为优于另一个可行函数的函数F2如果对于所有参数i, ICSi(F1)并不比ICSi(F2)差,然后

  • […]
  • F1F2是函数模板专门化,F1的函数模板专门化程度更高
  • 根据14.5.6.2中描述的部分排序规则为F2替换模板。

确定偏序的规则可以归结为:对于每个模板形参,合成一个新的类型/值,并尝试用它调用另一个重载。对于第一个重载,它变成operator+(Num<A>, Num<B>),不能调用operator+(Num<N>, Num<-N>)。然而,第二个重载变成了operator+(Num<C>, Num<-C>),您可以使用它调用第一个重载。

因此,接受Num<-N>的重载比接受Num<M>的重载更专门化,因此应该明确地优先选择它。