C++ 是否使用一个参数进行模板参数推断?
假设我有一个模板函数,C++ 是否使用一个参数进行模板参数推断?,c++,templates,template-argument-deduction,C++,Templates,Template Argument Deduction,假设我有一个模板函数,assign()。它接受一个指针和一个值,并将该值分配给指针的目标: template <typename T> void assign(T *a, T b) { *a = b; } int main() { double i; assign(&i, 2); } 有没有一种方法可以声明assign(),使第二个参数不参与模板参数的推导?只是将值2推导为类型int,该类型与&I推导的模板参数不匹配。您需要将该值用作双精度: assig
assign()
。它接受一个指针和一个值,并将该值分配给指针的目标:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, 2);
}
有没有一种方法可以声明
assign()
,使第二个参数不参与模板参数的推导?只是将值2
推导为类型int
,该类型与&I
推导的模板参数不匹配。您需要将该值用作双精度:
assign(&i, 2.0);
为什么不使用两种独立的参数类型,一种用于源,另一种用于目标
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;
}
template void assign(D*a,sb){*a=b;}
int main(int argc,char*argv[])
{
双i;
分配(&i,2);
返回0;
}
如果赋值不可能,模板实例化将无法编译。问题是编译器正在从第一个和第二个参数推断冲突信息。从第一个参数推断出
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; }
不能转换为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; }
#包括 模板 无效分配(T*a,U-b) { 静态断言(std::是可转换的::值, “错误:源类型无法转换为目标类型。”); *a=b; }
template<typename T>
void assign( T* a, typename std::identity<T>::type b );
模板
无效分配(T*a,类型名称std::identity::类型b);
- 演示:
这个答案的早期版本建议使用C++11中引入的模板别名功能。但模板别名仍然是一个可推断的上下文。
std::identity
和std::remove_reference
阻止推断的主要原因是模板类可以被专门化,因此即使您有一个模板类型参数的typedef,另一个专门化也可能有相同类型的typedef。由于可能存在歧义,因此无法进行推断。但是模板别名排除了专门化,因此仍然会发生推断。或者,您可以使用decltype
将第二个参数类型转换为第一个参数的类型
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, (decltype(i))2);
}
template void assign(T*a,tb){*a=b;}
int main(){
双i;
分配(&i,(decltype(i))2);
}
我的尝试如下所示:
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);
}
上述操作将保存1
move
。如果您有一个移动成本很高的类,或者一个仅复制且复制成本很高的类,这可以节省大量的成本。显然std::identity
不再存在()
但调用函数时,可以在参数类型列表中指定参数类型:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign<double>(&i, 2);
}
template void assign(T*a,tb){*a=b;}
int main(){
双i;
分配(&i,2);
}
通过这种方式,编译器将整数输入参数转换为double以匹配函数模板,而不进行参数推导
C++20具有
std::type_identity
,可用于建立非推断上下文:
#包括
模板
无效分配(T*a,std::type_identity_T b){
*a=b;
}
int main(){
双i;
分配(&i,2);
}
或
static\u assert
在函数中,因为在不可转换的情况下没有另一个重载。@Praetorian:或者,是的。我没有考虑它,因为函数很小,错误消息无论如何都是可以理解的,但你是对的,这是一个选项,担心后期编辑-这是一个相当合法的答案。“至少,我投了赞成票。@Sidnicious:没问题:)我知道。这是可行的,但它是脆弱和丑陋的-如果i
是一个无符号长的
,那么我的第二个参数必须是2ul
,如果它是一个浮点,它必须是(float)2
,等等。在真正的代码中,这需要一些争论,而且速度很快。@Sidnicious那么我想你可能会同意安迪的答案。奇怪的是,源代码使用D
,源代码使用s
destination@Ben:确实:)刚刚交换了它们。如果要这样做,为什么不完美地转发S
?+1。考虑到问题的编辑(我错过了),这似乎是正确的答案。这是一个非常酷的答案,但对我来说并不适用。我从编译器中得到了相同的“冲突类型”错误:ideone.com/gijbj1看起来像std::remove\u reference
也是等效的(如果可读性稍差)。@Sidnicious:my bad,看起来模板别名仍然是可推断的:(没有std::identity
,是吗?因此,上面的一个问题是效率低下。假设T
是std::vector
。参数b
按值取值,然后复制(不移动)到a
。一个小的改进可能是将assign
的实现更改为*a=std::move>(b)
,对于基元类型来说,这不需要花费什么,对于复杂类型可以节省很多。一个很大的改进是完善forwardb
@Yakk完全同意-我只是把它作为一个函数的例子来写的,这个函数需要指向同一类型的指针和值。实际上,它只需要基元,比这个家伙更有用:).是的,但你必须记住每次打电话都要这么做。事情很快变得很糟糕-我发这个问题是因为我是w
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);
}
template<typename T>
void assign( T* dest, typename std::identity<T>::type src ) {
*dest = std::move(src);
}
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign<double>(&i, 2);
}