【问题标题】:Why can't I construct a gsl::span with a brace-enclosed initializer list为什么我不能用大括号括起来的初始化列表构造一个 gsl::span
【发布时间】:2016-02-25 18:42:25
【问题描述】:

根据C++ Core Guidelines,我应该use a gsl::span to pass a half-open sequence

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

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 << " ";
}

它的好处是它不会假设调用者将他们的数据放在vector 中,或者强制他们构造一个临时的vector。例如,他们可以传递std::array

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

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

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

错误 C2664: 'void func(gsl::span)' : 无法转换 参数 1 从 'initializer-list' 到 'gsl::span'

它看起来像 gsl::span has a templated constructor 设计用于容纳任何容器。

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

【问题讨论】:

  • 花括号列表不是容器。
  • @KerrekSB std::initializer_list 是一个容器吗?
  • 不,不是。这是一种表示初始化程序列表的语言支持类型...但即使是这样,那也无济于事,因为您的参数不是std::initializer_list&lt;int&gt; 类型。这只是一个花括号列表。
  • @KerrekSB 好的,然后提到使用容器的模板化构造函数有点让人分心。但我的问题仍然存在。
  • 嗯,答案是gsl::span没有提供合适的构造函数...

标签: c++ c++11 cpp-core-guidelines


【解决方案1】:

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

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

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

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


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

所以,虽然这样可以:

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

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

gsl::span<const int> data{ 0,1,2,3 };
func(data);

【讨论】:

  • @NicolBolas:谢谢,我已将此添加到我的答案中。也许你可以看看,编辑是否正确。
  • 是的,这就是基本思想。
  • 请注意,您的“修复”示例不起作用,而且它应该是 4 而不是 3 int std:::array。看到这个:godbolt.org/z/zj7nGagodbolt.org/z/Tv7eMY
  • @whn:谢谢!您当然对数组大小是正确的(已修复),但 func 采用该代码适用的 span&lt;const int&gt; 而不是您的上帝螺栓链接中的 span&lt;int&gt;
  • @MikeMB 啊,我没有意识到 const 会有任何不同的行为,这确实解决了它。 godbolt.org/z/fTqqzn
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    这现在适用于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});
    }
    

    【讨论】:

      猜你喜欢
      • 2012-05-04
      • 1970-01-01
      • 2016-12-11
      • 2012-11-21
      • 2013-08-23
      • 2021-12-31
      • 2016-04-14
      • 1970-01-01
      • 2020-06-02
      相关资源
      最近更新 更多