假设我有一个模板函数assign()
。它接受一个指针和一个值,并将值赋给指针的目标:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, 2);
}
在这种情况下,我总是希望从第一个参数中推导出T
,但看起来我没有做好表达这一点的工作。 2
的类型为int
,因此:
deduce.cpp:5:5: error: no matching function for call to 'assign' assign(&i, 2); ^~~~~~ deduce.cpp:1:28: note: candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'int') template void assign(T *a, T b) { *a = b; }
有没有办法可以声明assign()
,以便第二个参数不参与模板参数扣除?
答案 0 :(得分:13)
使用两个类型参数可能是最好的选择,但是如果你真的只想从第一个参数执行推论,只需要使第二个不可推导:
template<typename T>
void assign( T* a, typename std::identity<T>::type b );
此答案的早期版本建议使用C ++ 11中引入的模板别名功能。但模板别名仍然是一种可推导的背景。 std::identity
和std::remove_reference
阻止演绎的主要原因是模板类可以是专用的,因此即使你有一个模板类型参数的typedef,另一个专业化也可能有一个typedef相同类型。由于可能存在歧义,因此不会进行演绎。但模板别名排除了专业化,因此仍然会进行演绎。
答案 1 :(得分:4)
问题在于编译器正在从第一个和第二个参数中推断出冲突的信息。从第一个参数开始,它将T
推导为double
(i
是double)
;从第二个参数,它推导出T
为int
(2
的类型是int
)。
这里有两个主要的可能性:
始终明确您的参数类型:
assign(&i, 2.0);
// ^^^^
或者让您的功能模板接受两个模板参数:
template <typename T, typename U>
void assign(T *a, U b) { *a = b; }
在这种情况下,您可能希望对模板进行SFINAE约束,以便在U
无法转换为T
的情况下,它不会分离到重载决策:
#include <type_traits>
template <typename T, typename U,
typename std::enable_if<
std::is_convertible<U, T>::value>::type* = nullptr>
void assign(T *a, U b) { *a = b; }
如果您不需要在U
无法转换为T
时从过载集中排除您的函数,您可能希望在assign()
中有一个静态断言以产生更好的效果编译错误:
#include <type_traits>
template<typename T, typename U>
void assign(T *a, U b)
{
static_assert(std::is_convertible<T, U>::value,
"Error: Source type not convertible to destination type.");
*a = b;
}
答案 2 :(得分:3)
只是将值2
推断为类型int
,这与&i
推导出的模板参数不匹配。您需要将该值用作double:
assign(&i, 2.0);
答案 3 :(得分:2)
为什么不使用两个独立参数类型,一个用于源,一个用于目标?
template <typename D, typename S> void assign(D *a, S b) { *a = b; }
int main(int argc, char* argv[])
{
double i;
assign(&i, 2);
return 0;
}
如果无法进行分配,则模板实例化将无法编译。
答案 4 :(得分:1)
我的尝试看起来像这样:
template<typename T, typename U>
typename std::enable_if< std::is_convertible< U&&, T >::value >::type // not quite perfect
assign( T* dest, U&& src ) {
*dest = std::forward<U>(src);
}
第二个参数是您可以转换为T
的任何内容,但我们通过通用引用将其视为有条件地将其移至*dest
。我在签名中测试可转换性而不是让主体无法编译,因为找不到重载似乎比没有编译主体更有礼貌。
与简单相比:
template<typename T>
void assign( T* dest, typename std::identity<T>::type src ) {
*dest = std::move(src);
}
以上节省1 move
。如果你有一个昂贵的移动课程,或者一个只复制且复制费用昂贵的课程,这可以节省很多。
答案 5 :(得分:0)
或者,您可以使用decltype
将第二个参数类型化为第一个参数。
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, (decltype(i))2);
}
答案 6 :(得分:0)
显然std::identity
不再存在(Is there a reason why there is not std::identity in the standard library?)
但是,在调用函数时,可以在参数类型列表中指定参数类型:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign<double>(&i, 2);
}
通过这种方式,编译器会将整数输入参数转换为double以匹配函数模板,而不会产生参数推断。
答案 7 :(得分:0)
C ++ 20具有std::type_identity
,可用于建立非推论上下文:
#include <type_traits>
template <typename T>
void assign(T *a, std::type_identity_t<T> b) {
*a = b;
}
int main() {
double i;
assign(&i, 2);
}