我很惊讶地发现在以下代码中无法成功推断出T
:
template <typename T>
void Set(T* a,T b)
{
*a = b;
}
void Test()
{
unsigned long a;
Set(&a, 1);
}
VC ++抱怨含糊不清:
1>test.cpp(10): error C2782: 'void Set(T *,T)' : template parameter 'T' is ambiguous
1> test.cpp(32) : see declaration of 'Set'
1> could be 'int'
1> or 'unsigned long'
显然,可以通过将呼叫更改为Set(&a, 1ul);
来解决此问题,但我的问题是为什么这是必要的?
为什么不能使用指针的类型来推导T
,这应该是明确的?
有没有办法重写模板,以便对Set
的原始调用成功编译?
答案 0 :(得分:6)
为什么不能使用指针的类型推导出T,这应该是 不含糊?
因为您没有告诉编译器这样做。模板参数的扣除不一致会导致扣减失败。
有没有办法重写模板以便原始调用 Set会成功编译吗?
是的,使用非推断的上下文。
template <typename T>
struct identity {using type=T;};
template <typename T>
using identity_t = typename identity<T>::type;
template <typename T>
void Set(T* a, identity_t<T> b) {
*a = b;
}
Demo。
或者使用第二个模板参数。
答案 1 :(得分:3)
参数T* a
和T b
都是推导出的上下文,因此首先编译器会尝试从它们中推导出T
,以便T*
与第一个参数的类型相同,T
与相同,与第二个参数的类型相同(遵循通常的衰减调整)。这从第一个参数产生unsigned long
,从第二个参数产生int
,因此这种推导尝试失败。
然后编译器将尝试查找T
,使第一个参数可转换为 T*
,第二个参数可转换为 { {1}}(而不是类型相同)。但是,在这种情况下,只考虑某些转换。参见N3936中的[temp.deduct.call],
(4)通常,演绎过程会尝试查找模板参数值,以便推导出
T
与A
相同(在如上所述转换类型A
之后)。但是,有三种情况允许 差异:(4.1) - 如果原始
A
是引用类型,则推导出的P
(即引用所指的类型)可以是 比转换后的A
更符合cv资格。(4.2) - 转换后的
A
可以是另一个指向成员类型的指针或指针,可以转换为 通过资格转换(4.4)推断出A
。(4.3) - 如果
A
是一个类而P
的格式为 simple-template-id,,则转换后的P
可以是派生的一类 推导出的A
。同样,如果A
是指向 simple-template-id形式的类的指针,则转换后的P
可以是指向推导出的A
指向的派生类的指针。(5)只有在类型扣除失败的情况下才考虑这些替代方案。如果他们产生不止一个 可能推导出
A
,类型推导失败。 [注意:如果在任何一个中没有使用模板参数 函数模板的函数参数,或者仅在非推导的上下文中使用,其对应 无法从函数调用中推断出 template-argument ,并且必须明确 template-argument 指定。 - 结束记录]
对于推导上下文中的参数类型,不考虑整数转换,例如A
到int
。因此,unsigned long
在这种情况下也不可能是T
。因此,unsigned long
的推断完全失败。
我的建议是:
T
答案 2 :(得分:0)
它含糊不清,因为它可能都是
int*
和int
(因为1是int
)unsigned long*
和unsigned long
(因为您的指针是unsigned long*
)答案 3 :(得分:0)
值1推导为类型int
,与&a
推导出的模板参数不匹配。
有没有办法重写模板以便原始调用 设置将成功编译
两种参数方式:(仅适用于完整性)
#include <type_traits>
// SFINAE : in case S is not convertible to T
template <typename T, typename S,
typename std::enable_if<
std::is_convertible<S, T>::value>::type* = nullptr >
void Set(T *a, S b)
{
*a = b;
}