对于带有表达式模板的类,我在重载运算符的返回类型推导期间偶然发现了以下错误。以下示例说明了错误:
template < typename T >
struct A_Number {
T x;
};
// TAG1
template < typename T >
A_Number< T >
operator+(const A_Number< T > &a, const A_Number< T > &b)
{
return {a.x + b.x};
}
// TAG2
template < typename T, typename S >
A_Number< T >
operator+(const A_Number< T > &a, const S &b)
{
return {a.x + b};
}
// TAG3
template < typename T, typename S >
auto
operator+(const S &b, const A_Number< T > &a) -> decltype(a + b)
// ^^^^^
{
return a + b;
}
int
main(void)
{
auto x1 = A_Number< int >{1};
auto x2 = A_Number< int >{1};
auto res1 = x1 + 1; // instantiates TAG2
auto res2 = 1 + x1; // instantiates TAG3, TAG2
auto res3 = x1 + x2; // error, tries to match TAG3
return EXIT_SUCCESS;
}
当尝试使用g ++ - 5或clang ++编译时,我收到此错误
fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
operator+(const S &b, const A_Number< T > &a) -> decltype(a + b)
显然,编译器会尝试匹配版本TAG3,但可以使用更好的匹配(TAG1)。当试图匹配时,他们试图推断出似乎导致TAG3的递归实例化的返回类型。为什么返回类型推导不能看到其他(更好匹配)重载?即使另一个重载的模板函数具有更好的匹配签名,推导返回类型也是正确的行为吗?
有趣的是,当完全省略返回类型并使用c++14
进行编译时,此错误会在空气中蒸发,如下所示:
// TAG3
template < typename T, typename S >
auto
operator+(const S &b, const A_Number< T > &a) // C++14
{
return a + b;
}
不可否认,这是一个学术问题,因为可以采用变通方法。但任何人都可以阐明这种行为是标准符合还是编译器错误?
答案 0 :(得分:0)
这实际上是编译器部分的正确行为。重载分辨率有两个步骤,来自[over.match]:
- 首先,候选函数的一个子集(那些具有适当数量的参数并满足 选择某些其他条件)以形成一组可行的功能(13.3.2) - 然后根据所需的隐式转换序列(13.3.3.1)选择最佳可行函数 将每个参数与每个可行函数的相应参数相匹配。
x1 + x2
的候选函数是:
// TAG1
template < typename T >
A_Number< T >
operator+(const A_Number< T > &a, const A_Number< T > &b);
// TAG2
template < typename T, typename S >
A_Number< T >
operator+(const A_Number< T > &a, const S &b);
// TAG3
template < typename T, typename S >
auto operator+(const S &b, const A_Number< T > &a) -> decltype(a + b)
好的,我们需要先确定TAG3
的返回类型,然后再选择最适合的候选人。现在,operator+
是依赖名称,因为它取决于两个模板参数。所以根据[temp.dep.res]:
在解析依赖名称时,会考虑以下来源的名称:
- 在模板定义点可见的声明 - 来自名称空间的声明与函数参数的类型相关联 实例化上下文(14.6.4.1)和定义上下文。
因此,要解决TAG3
的返回类型,我们需要在a+b
上执行查找和重载解析。这再次给了我们相同的三个候选人(前两个找到了通常的方式,TAG3
通过ADL再次找不到帮助),所以我们四处走走。
您的解决方案是:
TAG3
的尾随返回类型。这将停止旋转木马,我们将从我们的三个可行的候选人开始,其中TAG1
是最专业的,因此它将被选为最佳可行候选人。如果TAG3
为S
,则阻止A_Number<T>
被实例化。例如,我们可以为A_Number
:
template <typename T>
struct is_A_Number : std::false_type { };
template <typename T>
struct is_A_Number<A_Number<T>> : std::true_type { };
// TAG3
template < typename T, typename S,
typename = typename std::enable_if<!is_A_Number<S>::value>::type>
auto operator+(const S &b, const A_Number< T > &a) -> decltype(a + b);
即使在C ++ 11中也会编译。