使用一个参数进行模板参数推导
Use one argument for template parameter deduction?
假设我有一个模板函数,assign()
.它接受一个指针和一个值,并将值分配给指针的目标:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, 2);
}
在这种情况下,我总是希望从第一个论点中推断出T
,但看起来我没有很好地表达这一点。 2
的类型是int
,所以:
推断.cpp:5:5:错误:调用"分配"没有匹配函数 分配(&i, 2(; ^~~~~~推断.cpp:1:28:注意:忽略候选模板:推断参数"T"的冲突类型("双精度"与"整数"(template void assign(T *a, T b( { *a = b; }
有没有办法声明assign()
,以便第二个参数不参与模板参数推断?
使用两个类型参数可能是最好的选择,但如果你真的想只从第一个参数执行推导,只需使第二个参数不可推导:
template<typename T>
void assign( T* a, typename std::identity<T>::type b );
- 演示:http://ideone.com/ZW6Mpu
此答案的早期版本建议使用 C++11 中引入的模板别名功能。 但模板别名仍然是可推导的上下文。 std::identity
和 std::remove_reference
防止推导的主要原因是模板类可以专用化,因此即使您具有模板类型参数的 typedef,另一个专用化也可能具有相同类型的 typedef。 由于可能存在歧义,因此不会进行扣除。 但是模板别名排除了专用化,因此仍然会发生演绎。
问题是编译器从第一个和第二个参数中推断出相互冲突的信息。从第一个参数,它推导出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; }
如果当函数不可转换为
T
时,您不需要从重载集中排除U
,您可能希望在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
的值推导为类型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<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
. 如果您有一个昂贵的移动类,或者一个仅复制且复制成本高昂的类,这可以节省大量资金。
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);
}
演示
,可以使用decltype
将第二个参数类型转换为第一个参数的类型。
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign(&i, (decltype(i))2);
}
显然std::identity
不再存在(标准库中没有 std::identity 的原因是什么?
但是,在调用函数时,您可以在参数类型列表中指定参数类型:
template <typename T> void assign(T *a, T b) { *a = b; }
int main() {
double i;
assign<double>(&i, 2);
}
这样,编译器会将整数输入参数转换为双精度以匹配函数模板,而不会扣除参数。
现场演示
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 如何制作一个将函数作为参数的类方法
- 修改函数中的指针(将另一个指针作为参数传递)
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 构造函数在退出函数时无法初始化一个参数
- 在C++中声明一个函数时,它需要有函数本身的参数吗
- visual是否可以在c++中创建一个接收无限数量相同类型(或至少相当数量)参数的函数
- 如何将一个类的函数作为另一个类的另一个函数的参数传递
- 表达式 SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本
- C++重载函数,一个采用基类的参数,另一个采用派生类的参数
- 运算符重载:"operator+"必须采用零个或一个参数
- 是否可以在C++中有一个"generic"模板参数,该参数可以是非类型模板参数或类型?
- 如果需要转换,我可以在读取参数的同时将其移动到另一个参数吗?
- 如何在另一个函数中使用返回值作为参数?
- 如果模板参数是另一个模板的实例化,则键入特征测试
- 将参数一个接一个地传递,或通过将它们包裹在数组,结构或元组中
- 如何给一个参数一个由其他参数的函数确定的默认值
- C++如何给一个参数一个特定的枚举,它可以是
- 如果我在默认构造函数中放了一个参数,但给了这个参数一个默认值,它还是一个默认构造函数吗
- 为什么在c++中给typename模板参数一个默认值0 ?