为什么我不能将 std::span<int> 传递给采用 std::span<const T> 的函数模板?

问题描述 投票:0回答:2

我正在使用 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
会出现此错误,而使用指针则不会?

c++ templates type-deduction std-span
2个回答
1
投票

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>
    ,并且编译器将正确识别并使用它。

0
投票

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

注意:在大多数情况下,连续的范围是多余的,您可以使用随机访问范围或一些较弱的要求。

© www.soinside.com 2019 - 2024. All rights reserved.