15

根据C++ Core Guidelines,我应该使用 gsl::span 来传递半开序列

我认为这意味着不要编写如下函数:

void func(const std::vector<int>& data) {
    for (auto v : data) std::cout << v << " ";
}

我应该更喜欢:

void func(gsl::span<const int> data) {
    for (auto v : data) std::cout << v << " ";
}

这样做的好处是它不假设调用者将他们的数据放在 a 中vector,或者强制他们构造一个临时的vector. 例如,他们可以通过一个std::array

但是一个常见的用例是传递一个大括号括起来的初始化列表:

func({0,1,2,3})

这适用于采用 a 的函数,std::vector但对于采用 a 的函数,gsl::span我收到错误消息:

错误 C2664:“void func(gsl::span)”:无法将参数 1 从“initializer-list”转换为“gsl::span”

它看起来gsl::span 有一个模板化的构造函数,旨在接受任何容器。

这只是 Microsoft GSL 实施中缺少的东西,还是有充分的理由阻止这种做法?

4

3 回答 3

12

当你调用向量版本时,初始化列表用于创建一个临时std::vector的,然后通过 const 引用传递给函数。这是可能的,因为std::vector有一个以 astd::initializer_list<T>作为参数的构造函数。
但是,gsl::span没有这样的构造函数并且{0,1,2,3}没有类型,它也不能被您提到的模板化构造函数接受(除此之外,这std::initializer_list<T>无论如何都不会满足容器概念)。

一个(丑陋的)解决方法当然是显式创建一个临时数组:

func(std::array<int,4>{ 0,1,2,3 });

我看不出有什么特别的原因,为什么gsl::span不应该有一个接受 a 的构造函数std::initializer_list,但请记住,这个库仍然很新并且正在积极开发中。因此,也许这是他们忽略的东西,没有时间实施,不确定如何正确执行,或者确实有一些细节,这会使该构造变得危险。最好直接在 github 上询问开发人员。


编辑:
正如@Nicol Bolas 在他的评论中解释的那样,这是设计使然,因为像(和其中的元素)这样的初始化列表{0,1,2,3}是一个临时对象,并且gsl::span它本身不是一个容器(它不拥有元素的所有权),他们认为很容易意外地创建一个gsl::span包含对这些临时元素的悬空引用的。

所以,虽然这样可以:

func({ 0,1,2,3 });

因为初始化列表的生命周期在函数完成后结束,这样的事情会创建一个悬空引用:

gsl::span<const int> data{ 0,1,2,3 };
func(data);
于 2015-11-24T00:36:19.260 回答
4

跨度是非拥有的。不拥有存储空间。它是指针算法的替代品,而不是存储类。

你需要把你的数据放在一个存储类中,然后如果你想用指针算法做一些聪明的事情,你可以用 span 做一些聪明的事情。

您不能使用初始化列表来初始化跨度,因为没有地方可以放置数据。

于 2017-12-28T03:09:22.970 回答
1

这现在适用于absl::Span. 以下示例从https://abseil.io/tips/93复制:

void TakesSpan(absl::Span<const int> ints);

void PassALiteral() {
  // Span does not need a temporary allocation and copy, so it is faster.
  TakesSpan({1, 2, 3});
}
于 2021-12-08T14:38:18.637 回答