我正在使用 C++ 中的模板和类型推导,并且在使用
std::span
但不使用原始指针时遇到类型推导失败。下面是我的代码的简化版本:
#include <span>
#include <vector>
template <typename T>
void f1(std::span<const T> param)
{}
template <typename T>
void f2(const T* param)
{}
int main()
{
std::vector<int> v{1,2,3};
std::span<int> s{v};
// Uncommenting this line causes a compilation error:
// cannot deduce a type for 'T' that would make 'const T' equal 'int'
// f1(s);
int x = 10;
int* px = &x;
const int* z = px;
f2(px); // Works fine
f2(z); // Works fine
}
当我取消注释
f1(s)
调用时,我收到一条编译错误,指出编译器无法推导出 T
的类型,从而使 const T
等于 int
。但是,类似的指针模板函数(如 f2
)在传递 int*
和 const int*
时编译不会出现任何问题。
为什么使用
std::span
会出现此错误,而使用指针则不会?
std::span<T>
可以隐式转换为 std::span<const T>
。因此,如果函数中的 T
是 int
,则您的 std::span<int>
参数可以转换为函数所需的 std::span<const int>
。然而,这并不意味着编译器能够在其模板参数推导过程中弄清楚这一点。
编译器的模板参数推导过程并不关心什么可以转换成什么。它需要
T
的一些参数,以便 std::span<const T>
与 std::span<int>
相同,正如错误消息中所说,没有参数,因为没有。如果 T
是一个 int
,正如我上面所说,std::span<const T>
将是一个 std::span<const int>
,可以从 std::span<int>
隐式转换。 但它仍然不是编译器想要的完全相同的类型,因此该选项被排除在外。
您可以通过两种方式解决此问题:
f1<int>(s)
而不是f1(s)
。这避免了任何模板参数推导。s
变为 std::span<const int>
,而不是像当前那样成为 std::span<int>
。现在确实存在一个类型 T
,使得 std::span<const T>
是一个 std::span<const int>
,并且编译器将正确识别并使用它。T
中的std::span<const T>
无法从std::span<int>
推出,因为int
不是const
。
确实存在从任何 std::span<U>
到 std::span<const U>
的隐式转换,但在模板参数推导过程中不考虑此类隐式转换。
另请参阅为什么隐式类型转换在模板推导中不起作用?
const T* param
是一种特殊情况,因为有专门的规则来规定何时扣除不能为您提供 T
的精确匹配,并且必须添加 const
(通过资格转换) ([temp.deduct.call ] p4:
一般来说,推导过程尝试找到模板参数值,使推导的 A 与[参数类型] A 相同(在类型 A 如上所述进行转换之后)。 但是,存在三种情况允许存在差异:
- [...]
- 转换后的A可以是另一个指针或指向成员的指针类型,可以通过函数指针转换和/或限定转换转换为推导的A。
在您的情况下,参数类型 A 是
int*
,可以转换为 const int*
从而匹配 const T*
。
在大多数情况下,您不应该编写带有
std::span<T>
的模板。
结果总是会有些混乱和不符合人体工程学。
因为 f1
无论如何都是一个模板,所以你不会因为这样写而丢失任何东西:
template <std::ranges::contiguous_range R>
void f1(R&& range);
这也可以让你在没有跨度的情况下传递
std::vector
和 std::string_view
。
注意:在大多数情况下,连续的范围是多余的,您可以使用随机访问范围或一些较弱的要求。