我有以下问题:
template< typename T >
class A
{};
class B
{
public:
template< typename... T >
void operator()( A<T>... a )
{
std::cout << "A<T>\n";
}
template< typename callable >
void operator()( callable f )
{
std::cout << "callable\n";
}
};
int main()
{
B b;
A<int> a;
b( a );
}
调用b( a )
不明确 - 我期望输出A<T>
,即执行operator()
的第一个定义。有谁知道如何解决它?
答案 0 :(得分:9)
直到并包括标准工作草案N4606(这包括已发布的C ++ 11和C ++ 14标准),调用确实含糊不清,并且Clang拒绝它是正确的。最新草案N4618中引入的更改使部分排序规则选择A<T>...
重载。这是一个非常近期的变化;我们需要给编译器一些时间来实现它。
MSVC 2015 U3和EDG 4.11选择A<T>...
重载,因此它们之前不符合要求,并且在这方面神奇地符合最新草案。
在模板参数推导之后,我们有两个重载,它们都是模板特化,并且基于转换(两者的标识转换,显然)同样好,所以重载解析必须求助于函数模板的部分排序。 / p>
该过程在[temp.deduct.partial]的标准中描述,我们对第8段感兴趣。在N4618草案之前,它说:
如果
A
是从函数参数包转换而P
不是a 参数包,类型扣除失败。否则,使用结果 类型P
和A
,然后按照中所述完成扣除 14.8.2.5。如果P
是函数参数包,则将参数模板的每个剩余参数类型的类型A
与 键入函数参数包的 declarator-id 的P
。每 比较推导出后续位置的模板参数 模板参数包由函数参数包扩展。如果 对于给定类型,演绎中的类型,演绎成功 模板被认为至少与来自的类型一样专业 参数模板。
(强调我的上下)
尝试从第一次重载到第二次重载时,A
是一个包而P
不是,所以上段中的第一句适用;扣除失败。反过来尝试推论,第三句和第四句适用,但是扣除再次失败,因为我们试图从一般形式A<T>
的参数中推导出callable
形式的参数的参数。
因此,演绎双向失败;两种模板都不比另一种更专业;这个电话很模糊。
该段的新措辞如下:
使用生成的类型
P
和A
,然后将扣除完成为 在14.8.2.5中描述。如果P
是函数参数包,则为类型 参数模板的每个剩余参数类型的A
是 与函数的 declarator-id 的类型P进行比较 参数包。每个比较推导出模板参数 模板参数包中的后续位置由扩展的 功能参数包。 同样,如果A
从a转换而来 功能参数包,与每个剩余参数进行比较 参数模板的类型。如果给定的扣除成功 类型,参数模板中的类型至少被认为是 与参数模板中的类型一样专门。
请注意,第一句话已经消失,并被强调的句子取代,这允许从包A
中扣除到非包装P
。
现在,由于新规则,扣除成功从A<T>
升级到callable
,但仍然无法相反(没有任何改变)。这使得第一次过载变得更加专业化。
快速修复:您可以向第一个重载添加一个前导非包装参数:
template<class T, class... Ts> void operator()(A<T>, A<Ts>...)
这将避免功能参数列表中第一个位置的包和非包装之间的比较。对于所有编译器,非包A<T>
显然比callable
更专业。
如果你需要一个匹配没有参数的调用的重载,请单独提供一个(抱歉......)。
答案 1 :(得分:3)
谢谢。
我解决了以下问题:
#include <iostream>
template< typename T >
class A
{};
class B
{
public:
template< typename... T >
void operator()( A<T>... a )
{
std::cout << "A<T>\n";
}
template< typename T >
void operator()( A<T> a )
{
std::cout << "A<T>\n";
}
template< typename callable >
void operator()( callable f )
{
std::cout << "callable\n";
}
};
int main()
{
B b;
A<int> a;
b( a );
b( );
}
这允许在没有参数的情况下调用operator()
。