我正在使用 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)。但是,当同时传递和 时,类似的指针模板函数(如)可以毫无问题地进行编译Tconst Tintf2int*const int*

为什么使用 时会出现此错误,std::span而使用指针时不会出现此错误?

0


最佳答案
2

T无法从中std::span<const T>推导出来,std::span<int>因为int不是const。确实存在从 anystd::span<U>到 的隐式转换std::span<const U>,但在模板参数推导过程中不会考虑此类隐式转换。另请参阅

const T* param是一种特殊情况,因为当推论不能给出完全匹配的结果时const T*,有专门的规则,const必须添加(通过资格转换)():

一般来说,推导过程会尝试找到模板参数值,使推导出来的A与 [参数类型] A相同(在类型A按照上述方式转换之后)。
但是,有三种情况允许存在差异:

  • […]
  • 转换后的A可以是另一个指针或指向成员的指针类型,可以通过函数指针转换和/或资格转换转换为推导的A。
  • […]

在您的情况下,参数类型Aint*,可以转换为const int*并因此匹配const T*

解决方案

在大多数情况下,由于这些推论问题,您不应该编写采用 的模板std::span<T>。结果总是会有些混乱和不符合人体工程学。由于f1无论如何都是模板,因此您不会因为编写以下内容而失去任何东西:

template <std::ranges::contiguous_range R>
void f1(R&& range);

这也可以让你传递,std::vectorstd::string_view无需先将它们转换为跨度。

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

1

  • 1
    P2998 可以解决这个问题。可能值得一提。


    – 

可以隐式转换std::span<T>std::span<const T>。因此,如果T函数中的 是int,则 的参数std::span<int>可以转换为std::span<const int>函数所需的 。但是,这并不意味着编译器能够在其模板参数推导过程中找出这一点。

编译器的模板参数推导过程并不关心什么可以转换为什么。它需要一些与 相同的T参数,并且正如错误消息中所说,没有这样的参数,因为没有。如果,正如我上面所说,将是 ,可以从 隐式转换但它仍然不是与编译器想要的完全相同的类型,因此排除了该选项。std::span<const T>std::span<int>Tintstd::span<const T>std::span<const int>std::span<int>

您可以通过两种方式解决这个问题:

  • 明确指定模板参数。因此,f1<int>(s)而不是f1(s)。这可以避免任何模板参数推导。
  • 或者,保留模板参数推导,并将sastd::span<const int>改为std::span<int>当前的 a。现在确实存在一个类型T,即std::span<const T>a std::span<const int>,并且编译器将正确识别并分配它。

2

  • int*const int*也需要转换


    – 

  • @463035818_is_not_an_ai 一个是通过调整 const-ness 进行本机类型操作,另一个是确定要使用类的哪个模板参数。除了最琐碎和最特殊的情况外,后者是不可行的。


    –