模板推导和隐式构造函数
template deduction and implicit constructors
有没有办法使模板推导与(隐式)转换一起工作?像下面的例子:
template<typename T> struct A {};
template<typename T> struct B
{
B(A<T>); // implicit A->B conversion
};
template<typename... Ts> void fun(B<Ts>...);
int main()
{
A<int> a;
fun(B(a)); // works
fun(a); // does not work (deduction failure)
}
我的想法:
- 如果
A
是 B
的子类,一切正常。这意味着演绎可以使用向上转换进行隐式转换。所以它不能使用构造函数进行隐式转换似乎很奇怪。
A
和B
的重载fun
原则上是可以的,但是对于多个参数,组合太多
- 添加扣除指南 (
template<typename T> B(A<T>)->B<T>;
) 不会改变任何内容。
编辑:一些背景:
在我的实际代码中,A
是一个(大)容器,而 B
是一个轻量级的非拥有视图对象。这种情况类似于在推导 T
期间 std::vector<T>
不能隐式转换为 std::span<T>
,即使对于任何具体的 T
,这种转换都存在。
模板参数推导不考虑任何潜在的类型转换 - 主要是因为推导发生在任何此类转换发生之前。
基本上,当编译器看到 fun(a)
时,它首先收集一组有资格为该调用提供服务的 foo
函数。如果找到 foo
函数模板,编译器会尝试通过用调用语句中传递的参数类型替换模板参数来从中生成具体函数。这就是模板参数推导发生的地方。这里不会发生类型转换,因为没有要转换成的具体类型。在您的示例中,B<T>
不是要转换为的类型,因为 T
是未知的,并且无法从类型 A<int>
的参数中发现它。也不知道 B<T>
的任意实例是否可以从 A<int>
构造,因为 B
的不同特化可能有不同的构造函数集。因此,在你的情况下推论失败。
如果推导成功,则将具体函数(具有推导的参数类型)添加到候选集中。
收集到候选集后,将选择最匹配的候选者。此时考虑参数转换。转换是可能的,因为候选者不再是模板,并且转换的目标类型是已知的。
要解决这个问题,您可以做的是让编译器也推导模板,然后显式构造 B<T>
:
template<typename... Ts> void fun_impl(B<Ts>...);
template<template<typename> class X, typename... Ts>
std::enable_if_t<(std::is_constructible_v<B<Ts>, X<Ts>> && ...)> fun(X<Ts>... args)
{
fun_impl(B<Ts>(args)...);
}
int main()
{
A<int> a;
fun(B(a)); // works, instantiates fun<B, int>
fun(a); // works, instantiates fun<A, int>
}
我认为你可以提供不同的重载 func
然后你想出并解决你的多个可能转换的问题。
template<typename T> struct A {};
template<typename T> struct C {};
template<typename T> struct B
{
B(A<T>) {}
B(C<T>) {}
};
template<typename... Ts>
void fun(B<Ts>...)
{
LOGFUN;
}
template<typename... Ts>
void fun(Ts...arg)
{
LOGFUN;
fun(B{arg}...);
}
int main()
{
A<int> a;
C<int> c;
fun(B(a));
fun(a);
fun(c, a, c);
}
有没有办法使模板推导与(隐式)转换一起工作?像下面的例子:
template<typename T> struct A {};
template<typename T> struct B
{
B(A<T>); // implicit A->B conversion
};
template<typename... Ts> void fun(B<Ts>...);
int main()
{
A<int> a;
fun(B(a)); // works
fun(a); // does not work (deduction failure)
}
我的想法:
- 如果
A
是B
的子类,一切正常。这意味着演绎可以使用向上转换进行隐式转换。所以它不能使用构造函数进行隐式转换似乎很奇怪。 A
和B
的重载fun
原则上是可以的,但是对于多个参数,组合太多- 添加扣除指南 (
template<typename T> B(A<T>)->B<T>;
) 不会改变任何内容。
编辑:一些背景:
在我的实际代码中,A
是一个(大)容器,而 B
是一个轻量级的非拥有视图对象。这种情况类似于在推导 T
期间 std::vector<T>
不能隐式转换为 std::span<T>
,即使对于任何具体的 T
,这种转换都存在。
模板参数推导不考虑任何潜在的类型转换 - 主要是因为推导发生在任何此类转换发生之前。
基本上,当编译器看到 fun(a)
时,它首先收集一组有资格为该调用提供服务的 foo
函数。如果找到 foo
函数模板,编译器会尝试通过用调用语句中传递的参数类型替换模板参数来从中生成具体函数。这就是模板参数推导发生的地方。这里不会发生类型转换,因为没有要转换成的具体类型。在您的示例中,B<T>
不是要转换为的类型,因为 T
是未知的,并且无法从类型 A<int>
的参数中发现它。也不知道 B<T>
的任意实例是否可以从 A<int>
构造,因为 B
的不同特化可能有不同的构造函数集。因此,在你的情况下推论失败。
如果推导成功,则将具体函数(具有推导的参数类型)添加到候选集中。
收集到候选集后,将选择最匹配的候选者。此时考虑参数转换。转换是可能的,因为候选者不再是模板,并且转换的目标类型是已知的。
要解决这个问题,您可以做的是让编译器也推导模板,然后显式构造 B<T>
:
template<typename... Ts> void fun_impl(B<Ts>...);
template<template<typename> class X, typename... Ts>
std::enable_if_t<(std::is_constructible_v<B<Ts>, X<Ts>> && ...)> fun(X<Ts>... args)
{
fun_impl(B<Ts>(args)...);
}
int main()
{
A<int> a;
fun(B(a)); // works, instantiates fun<B, int>
fun(a); // works, instantiates fun<A, int>
}
我认为你可以提供不同的重载 func
然后你想出并解决你的多个可能转换的问题。
template<typename T> struct A {};
template<typename T> struct C {};
template<typename T> struct B
{
B(A<T>) {}
B(C<T>) {}
};
template<typename... Ts>
void fun(B<Ts>...)
{
LOGFUN;
}
template<typename... Ts>
void fun(Ts...arg)
{
LOGFUN;
fun(B{arg}...);
}
int main()
{
A<int> a;
C<int> c;
fun(B(a));
fun(a);
fun(c, a, c);
}