为 std::span<T 添加成员函数 substr>它模仿 string_view::substr

adding member function substr for std::span<T> which imitates the string_view::substr

提问人:ollydbg23 提问时间:2/24/2023 最后编辑:ollydbg23 更新时间:2/28/2023 访问量:122

问:

在我的代码中,我想创建一个通过扩展类来命名的新类,但是新类具有一些额外的成员函数,这些函数在 中不存在。Spanstd::spanSpanstd::span

一种是将函数添加到 ,该函数已实现且工作正常。有关详细信息,请参阅我之前的问题:从 std::span 中删除第一个元素的方法是什么?remove_prefixstd::span

现在我想添加另一个名为的成员函数(我尝试模仿一些成员函数,因为我需要在我的设计中同时支持两者)。在旧代码中,我只有支持,所以它有一些代码片段,比如,现在我希望可以是通用的,这意味着它可以是或()。substrstd::string_viewstd::span<T>std::string_viewstd::string_viewsv.substr()svstd::string_viewstd::span<T>Span<T>

以下是我用于解决此问题的最小示例代码:

#include <iostream>
#include <span>

using namespace std;

// Span class which is derived class from std::span
// add remove_prefix and substr function which is similar like std::string_view
template<typename T>
class Span : public std::span<T>
{
public:
    // Inheriting constructors from std::span
    using std::span<T>::span;

    // add a public function which is similar to std::string_view::remove_prefix
    constexpr void remove_prefix(std::size_t n) {
        auto& self = static_cast<std::span<T>&>(*this);
        self = self.subspan(n);
    }

    // add another public function substr, which is the span::subspan
    constexpr Span substr(std::size_t pos, std::size_t count){
        auto& self = static_cast<std::span<T>&>(*this);
        auto ret = self.subspan(pos, count);
        return static_cast<Span<T>&>(ret);
    }
};

float arr[3] = {1.0, 2.0, 3.0};

int main()
{
    Span<float> a(arr, 2);
    Span<float> b = a.substr(0, 1);
    return 0;
}

上面的代码在支持 C++ 的 g++ 下构建并运行正常。我的问题是:我的实现正确吗?因为我在代码中使用了这么多。其实已经有了一个函数名,我做的就是做一个函数名别名。static_caststd::spansubspansubstr

有什么想法吗?谢谢。

C++ C++20 substr std-span

评论


答:

1赞 Georgij Krajnyukov 2/24/2023 #1

所以,我认为你应该考虑改变你的设计,而不是改变STL。

首先,从 派生是不安全的,因为它没有虚拟析构函数,所以有可能泄漏内存:是否可以从 STL 容器继承实现,而不是委托?std::span

其次,它们根本不是虚拟的,这意味着继承被错误地使用,因为在我看来,它的目的是创建要覆盖的接口,以便模块的细节可以相互隐藏。

很难说如果不了解您的设计,我会怎么做,但我建议一些解决方案:

  • 使用多态性或显式模板专用化 (with ) 来定义这两个类的方法。constexpr if
  • 为您的具体设计创建一个界面(解决您的最终目标),隐藏包含此视图之一的细节。
  • 您可以从其中之一构造并仅使用其中之一。std::span<const char>std::string_view

评论

0赞 Caleth 2/24/2023
std::span<T>不是一个容器,所以它不太可能是任何人的容器。您甚至可以通过私下继承并公开所有成员来确保没有人指向deletestd::span<T> *Span<T>
0赞 ollydbg23 2/24/2023
嗨,Georgij Krajnyukov,我的最终目标是实现一个 PEG 解析器,它可以解析 和 ,我有一些演示代码显示在 stackoverflow.com/questions/75331349/...。感谢您的帮助,我会阅读并考虑您的建议。std::string_viewstd::span<T>
0赞 Georgij Krajnyukov 2/24/2023
@Caleth,感谢您的回复!是的,语义上是一种观点,所以我对我的答案做了一点更正。但我想指出,第一种说法对于任何类型都是正确的。在多态基类中声明析构函数是虚拟的常见做法。这是Scott Meyers的Effective C++中的主题之一。std::span
0赞 ollydbg23 2/25/2023
嗨,Georgij Krajnyukov,我现在将 Caleth 的答案标记为最佳答案,它使用自由函数来处理不同的类型。但是你的回答也很有帮助,我的意思是我现在不使用我的派生类,我现在只是直接使用标准的 std 容器。谢谢。Spanstd::span
2赞 Caleth 2/24/2023 #2

你的实现没有错,但可以简化为remove_prefix

constexpr void remove_prefix(std::size_t n) {
    *this = subspan(n);
}

你的实现是错误的,因为你static_casting对一个对象的左值引用,该对象的最派生类型是 ,而不是 。您可以修复它:substrstd::span<T>Span<T>

constexpr Span substr(std::size_t pos, std::size_t count){
    return subspan(pos, count);
}

但是,我不会这样做。相反,我会更改调用代码以使用您重载的免费函数和(等)。std::span<T>std::string_view

template<typename T>
constexpr void remove_prefix(std::span<T> & span, std::size_t n) {
    span = span.subspan(n);
}

template<typename CharT, typename Traits>
constexpr void remove_prefix(std::basic_string_view<CharT, Traits> & view, std::size_t n) {
    view.remove_prefix(n);
}

template<typename T>
constexpr std::span<T> substr(std::span<T> span, std::size_t n) {
    return span.subspan(n);
}

template<typename CharT, typename Traits>
constexpr std::basic_string_view<CharT, Traits> substr(std::basic_string_view<CharT, Traits> view, std::size_t n) {
    return view.substr(n);
}

评论

0赞 ollydbg23 2/24/2023
“我会更改调用代码以使用您重载的免费函数”,谢谢,我会尝试您的方法。
0赞 ollydbg23 2/25/2023
嗨,Caleth,您的方法工作正常。您的代码中存在一个问题,需要两个参数来定位子字符串或子范围,例如: ,我已经在本地代码中更改了它,并且效果很好。谢谢你的帮助。substrstd::size_t pos, std::size_t count