3

when reading the document of std::span, I see there is no method to remove the first element from the std::span<T>.

Can you suggest a way to solve my issue?

The large picture of my problem(I asked in another question: How to instantiatiate a std::basic_string_view with custom class T, I got is_trivial_v<_CharT> assert error) is that I would like to have a std::basic_string_view<Token>, while the Token is not a trivial class, so I can't use std::basic_string_view, and someone suggested me to use std::span<Token> instead.

Since the basic_string_view has a method named remove_prefix which remove the first element, while I also need such kinds of function because I would like to use std::span<Token> as a parser input, so the Tokens will be matched, and consumed one by one.

Thanks.

EDIT 2023-02-04

I try to derive a class named Span from std::span, and add the remove_prefix member function, but it looks like I still have build issues:

#include <string_view>
#include <vector>
#include <span>


// derived class, add remove_prefix function to std::span
template<typename T>
class Span : public std::span<T>
{
public:
    // Inheriting constructors
    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) {
        *this = subspan(n);
    }
};


struct Token
{
    Token(){};
    Token(const Token& other)
    {
        lexeme = other.lexeme;
        type = other.type;
    }
    std::string_view lexeme;
    int type;
    // equal operator
    bool operator==(const Token& other)const {
        return (this->lexeme == other.lexeme) ;
    }
};

template <typename T>
struct Viewer;

template <>
struct Viewer<Token>
{
    using type = Span<Token>; // std::span or derived class
};

template <>
struct Viewer<char>
{
    using type = std::string_view;
};

template <typename T> using ViewerT = typename Viewer<T>::type;

template <typename T>
class Parser
{
    using v = ViewerT<T>;
};

// a simple parser demo

template <typename Base, typename T>
struct parser_base {
    using v = ViewerT<T>;
    constexpr auto operator[](v& output) const noexcept;
};

template<typename T>
struct char_ final : public parser_base<char_<T>, T> {
    using v = ViewerT<T>;
    constexpr explicit char_(const T ch) noexcept
        : ch(ch)
    {}

    constexpr inline bool visit(v& sv) const& noexcept {
        if (!sv.empty() && sv.front() == ch) {
            sv.remove_prefix(1);
            return true;
        }
        return false;
    }

private:
    T ch;
};

template <typename Parser, typename T>
constexpr bool parse(Span<T> &input, Parser const& parser) noexcept {
    return parser.visit(input);
}


int main()
{
    Token kw_class;
    kw_class.lexeme = "a";
    std::vector<Token> token_stream;
    token_stream.push_back(kw_class);
    token_stream.push_back(kw_class);
    token_stream.push_back(kw_class);

    Span<Token> token_stream_view{&token_stream[0], 3};

    auto p = char_(kw_class);
    parse(token_stream_view, p);

    return 0;
}

The build error looks like below:

[ 50.0%] g++.exe -Wall -std=c++20 -fexceptions -g  -c F:\code\test_crtp_twoargs\main.cpp -o obj\Debug\main.o
F:\code\test_crtp_twoargs\main.cpp: In member function 'constexpr void Span<T>::remove_prefix(std::size_t)':
F:\code\test_crtp_twoargs\main.cpp:52:17: error: there are no arguments to 'subspan' that depend on a template parameter, so a declaration of 'subspan' must be available [-fpermissive]
   52 |         *this = subspan(n);
      |                 ^~~~~~~
F:\code\test_crtp_twoargs\main.cpp:52:17: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
F:\code\test_crtp_twoargs\main.cpp: In instantiation of 'constexpr void Span<T>::remove_prefix(std::size_t) [with T = Token; std::size_t = long long unsigned int]':
F:\code\test_crtp_twoargs\main.cpp:113:29:   required from 'constexpr bool char_<T>::visit(v&) const & [with T = Token; v = Span<Token>]'
F:\code\test_crtp_twoargs\main.cpp:125:24:   required from 'constexpr bool parse(Span<T>&, const Parser&) [with Parser = char_<Token>; T = Token]'
F:\code\test_crtp_twoargs\main.cpp:141:10:   required from here
F:\code\test_crtp_twoargs\main.cpp:52:24: error: 'subspan' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
   52 |         *this = subspan(n);
      |                 ~~~~~~~^~~
F:\code\test_crtp_twoargs\main.cpp:52:24: note: declarations in dependent base 'std::span<Token, 18446744073709551615>' are not found by unqualified lookup
F:\code\test_crtp_twoargs\main.cpp:52:24: note: use 'this->subspan' instead
F:\code\test_crtp_twoargs\main.cpp:52:15: error: no match for 'operator=' (operand types are 'Span<Token>' and 'std::span<Token, 18446744073709551615>')
   52 |         *this = subspan(n);
      |         ~~~~~~^~~~~~~~~~~~
F:\code\test_crtp_twoargs\main.cpp:44:7: note: candidate: 'constexpr Span<Token>& Span<Token>::operator=(const Span<Token>&)'
   44 | class Span : public std::span<T>
      |       ^~~~
F:\code\test_crtp_twoargs\main.cpp:44:7: note:   no known conversion for argument 1 from 'std::span<Token, 18446744073709551615>' to 'const Span<Token>&'
F:\code\test_crtp_twoargs\main.cpp:44:7: note: candidate: 'constexpr Span<Token>& Span<Token>::operator=(Span<Token>&&)'
F:\code\test_crtp_twoargs\main.cpp:44:7: note:   no known conversion for argument 1 from 'std::span<Token, 18446744073709551615>' to 'Span<Token>&&'

Any idea on how to fix this issue?

Also, I don't know how to make a general parse function:

template <typename Parser, typename T>
constexpr bool parse(Span<T> &input, Parser const& parser) noexcept {
    return parser.visit(input);
}

Currently, the first argument of the parse should be a Viewer like type?

EDIT2023-02-05

Change the function as below, the above code can build correctly. This is from Benjamin Buch's answer.

    constexpr void remove_prefix(std::size_t n) {
        auto& self = static_cast<std::span<T>&>(*this);
        self = self.subspan(n);
    }

There is still one thing remains: How to generalize the parse function to accept both input types of std::string_view and Span<Token>?

If I change the parse function to this:

template <typename Parser, typename T>
constexpr bool parse(ViewerT<T> &input, Parser const& parser) noexcept {
    return parser.visit(input);
}

I got such compile error:

[ 50.0%] g++.exe -Wall -std=c++20 -fexceptions -g  -c F:\code\test_crtp_twoargs\main.cpp -o obj\Debug\main.o
F:\code\test_crtp_twoargs\main.cpp: In function 'int main()':
F:\code\test_crtp_twoargs\main.cpp:143:24: error: no matching function for call to 'parse(Span<Token>&, char_<Token>&)'
  143 |     bool result = parse(token_stream_view, p);
      |                   ~~~~~^~~~~~~~~~~~~~~~~~~~~~
F:\code\test_crtp_twoargs\main.cpp:125:16: note: candidate: 'template<class Parser, class T> constexpr bool parse(ViewerT<T>&, const Parser&)'
  125 | constexpr bool parse(ViewerT<T> &input, Parser const& parser) noexcept {
      |                ^~~~~
F:\code\test_crtp_twoargs\main.cpp:125:16: note:   template argument deduction/substitution failed:
F:\code\test_crtp_twoargs\main.cpp:143:24: note:   couldn't deduce template parameter 'T'
  143 |     bool result = parse(token_stream_view, p);
      |                   ~~~~~^~~~~~~~~~~~~~~~~~~~~~

Any ideas? Thanks.

BTW: I have to explicitly instantiation of the parse function call like:

bool result = parse<decltype(p), Token>(token_stream_view, p);

to workaround this issue.

ollydbg23
  • 1,124
  • 1
  • 12
  • 38
  • Thing to note, `string_view::remove_prefix` doesn't actually remove the element, it moves the view. Is that the correct behavior you are looking for? – Ranoiaetep Feb 03 '23 at 04:03
  • "*when reading the document of std::span, I see there is no method to remove the first element from the std::span.*" Are you reading carefully? Are the `first`, `last` and `subspan` in the Subviews section not meeting your needs? – 康桓瑋 Feb 03 '23 at 04:10
  • 2
    While [`subspan`](https://en.cppreference.com/w/cpp/container/span/subspan) and friends don't directly modify the span they're called on like `remove_prefix` does, you can assign the result back to the original object if that's the behavior you want. – Miles Budnek Feb 03 '23 at 04:13
  • Hi, thanks for your comments. I do know there are functions like get the `subspan` from the original span, but that way, I just create a new object, and assign back to the original one. While in parsing(I try to use a PEG parser), the consume of the token is so frequently, so that I'm not quite satisfied with the assignment way. – ollydbg23 Feb 03 '23 at 04:53
  • @Ranoiaetep Yes, I mean remove the first element from the token stream view. So, the view is one token shorter than the original one. – ollydbg23 Feb 03 '23 at 05:10
  • @ollydbg23 you do realise that span is two pointers? (or a pointer + a distance). `sp = sp.subspan(n);` can quite easily codegen as just arithmetic on registers – Caleth Feb 03 '23 at 09:57
  • @Caleth yes, that should be fast. – ollydbg23 Feb 03 '23 at 14:40
  • I have recently post another question: https://stackoverflow.com/questions/75552191/adding-member-function-substr-for-stdspant-which-imitates-the-string-views I just would like to add another member function to my derived class `Span`. It turns out that derived from a std container is not good idea, and I finally use `free functions` suggested by @Caleth . Thanks for your help. – ollydbg23 Feb 25 '23 at 03:13

2 Answers2

4

Call subspan with 1 as only (template) argument to get a new span, which doesn't contain the first element.

If you use a span with a static extend, you need a new variable because the data type changes by subspan.

#include <string_view>
#include <iostream>
#include <span>

int main() {
    std::span<char const, 12> text_a("a test-span");
    std::cout << std::string_view(text_a) << '\n';

    std::span<char const, 10> text_b = text_a.subspan<2>();
    std::cout << std::string_view(text_b) << '\n';
}

If you have a dynamic extend, you can assign the result to the original variable.

#include <string_view>
#include <iostream>
#include <span>

int main() {
    std::span<char const> text("a test-span");
    std::cout << std::string_view(text) << '\n';

    text = text.subspan(2);
    std::cout << std::string_view(text) << '\n';
}

The implementation of a modifying inplace subspan version is only possible for spans with a dynamic extend. It can be implemented as a free function.

#include <string_view>
#include <iostream>
#include <span>

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

int main() {
    std::span<char const> text("a test-span");
    std::cout << std::string_view(text) << '\n';

    remove_front(text, 2);
    std::cout << std::string_view(text) << '\n';
}

You can use your own spans derived from std::span if you prefer the dot-call.

#include <string_view>
#include <iostream>
#include <span>

template <typename T>
struct my_span: std::span<T> {
    using std::span<T>::span;

    constexpr void remove_front(std::size_t const n) noexcept {
        auto& self = static_cast<std::span<T>&>(*this);
        self = self.subspan(n);
    }
};

int main() {
    my_span<char const> my_text("a test-span");
    std::cout << std::string_view(my_text) << '\n';

    my_text.remove_front(2);
    std::cout << std::string_view(my_text) << '\n';
}

You can also write a wrapper class to call via dot syntax. This way you can additionally implement cascadable modification calls by always returning the a reference modifier class.

#include <string_view>
#include <iostream>
#include <span>

template <typename T>
class span_modifier {
public:
    constexpr span_modifier(std::span<T>& span) noexcept: span_(span) {}

    constexpr span_modifier& remove_front(std::size_t const n) noexcept {
        span_ = span_.subspan(n);
        return *this;
    }
    
private:
    std::span<T>& span_;
};

template <typename T>
constexpr span_modifier<T> modify(std::span<T>& span) noexcept {
    return span;
}

int main() {
    std::span<char const> text("a test-span");
    std::cout << std::string_view(text) << '\n';

    modify(text).remove_front(2).remove_front(5);
    std::cout << std::string_view(text) << '\n';
}

Note I use the template function modify to create an object of the wrapper class, because the names of classes cannot be overloaded. Therefore class names should always be a bit more specific. The function modify can also be overloaded for other data types, which then return a different wrapper class. This results in a simple intuitive and consistent interface for modification wrappers.

Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
  • Thank, in this method, I have to assign the new span to the original span. I'd expect directly change the original span. – ollydbg23 Feb 03 '23 at 05:12
  • 1
    @ollydbg23: Span has no modifying member functions (outside of assignment). – Nicol Bolas Feb 03 '23 at 05:21
  • Thanks, I see your edit of your answer, and by change the `remove_prefix` function to `constexpr void remove_prefix(std::size_t n) {auto& self = static_cast&>(*this); self = self.subspan(n);}` and I can successfully build the test code in my `Edit2023-02-04`. – ollydbg23 Feb 05 '23 at 01:34
  • @ollydbg23 You had two issues in your attempt. First `subspan` is a function template in a template base class, which requires either explicitly naming the base class by `std::span::subspan` or calling is by explicit `this->subspan` to instruct the compiler to search it in the base class. The second issue was, that you did try to call the assignment operator of your derived class instead of the one of the base class. My `static_cast` solves both. – Benjamin Buch Feb 05 '23 at 01:56
  • 1
    @BenjaminBuch thanks for the explanation, about the first issue, I have tried before by using `this->subspan`, but since the second issue still remains at that time. I still got compile error. With your answer, the two issue are all fixed. Great! – ollydbg23 Feb 05 '23 at 04:58
1

You can write remove_prefix of your version,

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

Demo

Nimrod
  • 2,908
  • 9
  • 20
  • Thanks. I have one question remains: Currently, the PEG parser working on the `std::string_view` correctly. Each sub parser has some kinds of function call like `input.remove_prefix(1)` to consume one Token. While if I would like to extent the parser to input as `std::span`, the old code need to be changed. Can I have some methods to keep the backward capability? If the user would like to use either types as input token stream? BTW: the parser code is located here: https://github.com/joemalle/limn/blob/master/limn.h – ollydbg23 Feb 03 '23 at 07:29
  • If I'm understanding correctly, it sounds like you wanna use `std::string_view` and `std::span` for different types. I suggest you make a class derived from `std::span` and make the `remove_prefix` a member function... – Nimrod Feb 03 '23 at 07:36
  • Yes, correct. My guess is that if I would like to set the base parser as a template, which need at least several arguments, one is `typename Token`, the other is input stream type `typename InputStream`. Then, the types could be `char + std::string_view` or `CustomToken + std::span`, something like this? – ollydbg23 Feb 03 '23 at 07:40
  • Nope, I would select the Viewer from the `typename Token`. Something like this,``` template struct Viewer; template <> struct Viewer { using type = std::span; // or derived class }; template <> struct Viewer { using type = std::string_view; }; template using ViewerT = typename Viewer::type; ``` – Nimrod Feb 03 '23 at 07:50
  • Thanks, what is the last statement used for: `template using ViewerT = typename Viewer::type; `, also, what if I need a member function like `std::span::remove_prefix`, because if I don't have such function, how can I keep the code the same for different viewer? – ollydbg23 Feb 03 '23 at 08:05
  • The `ViewerT` is an alias template for simpler use. You can use like this, ```template class Parser { using v = ViewerT; };``` – Nimrod Feb 03 '23 at 08:23
  • 2
    make a derived class from `std::span`, put the `remove_prefix` into its own member function. – Nimrod Feb 03 '23 at 08:24
  • I will try the way you suggest and report back. – ollydbg23 Feb 03 '23 at 14:42
  • Hi, I have just edit the original question, and adding a testing code, it still has some issues if I try to derive a class from `std::span` which adds `remove_prefix` member function. Can you have a look? Thanks. – ollydbg23 Feb 04 '23 at 13:16
  • Hi, @Nimrod, I have added and edit the original question, currently, `remove_prefix` function works OK now, but I still have problems about the `parse` function which need to support input type like `ViewerT`, thanks. It's in the `EDIT2023-02-05` section of the question. – ollydbg23 Feb 05 '23 at 07:28
  • Try `template constexpr bool parse(ViewerT &input, Parser const& parser) noexcept { return parser.visit(input); }` https://godbolt.org/z/W16frc4f8 I'm not sure if it works for other cases. Please provide more code if it fails. Thx – Nimrod Feb 05 '23 at 12:19
  • Oh, thanks. I see you have changed the function template's second parameter from `T` to `ViewerT`. I just tested it, and it build and work fine. Also, I have added a test case that both a `char` based parser and `Token` based parser works correctly. See the code in https://godbolt.org/z/P95Gqobsh – ollydbg23 Feb 06 '23 at 02:32
  • Very glad it helps! – Nimrod Feb 06 '23 at 02:38
  • Hi, it looks like I failed to implement the `operator >>`, which means first parse `a` and next to parse the char `b`. Here is test code: https://godbolt.org/z/Gncd7W7PW it looks like the error message is from the line `auto p = char_(kw_class) >> char_(kw_class);` which I try to construct a sequence parser. The error message said it can't deduce the type `T` of `template constexpr inline auto operator>>(Left&& left, Right&& right)`, so any ideas on how to fix this issue? I guess the `T` type should be get from the `Left` or `Right` type? – ollydbg23 Feb 07 '23 at 02:28
  • I have a really ugly solution about the problem in previous comment, see code here: https://godbolt.org/z/4rv35v9YK I just add a member `T mA` to `parser_base` class template, and later, I just such code: `template struct seq_ final : public parser_base, decltype(Left::mA)> {` Here, I use the `decltype(Left::mA)` to extract the type of the `T`. But adding a member variable is not a correct way to solve this issue. – ollydbg23 Feb 07 '23 at 03:14
  • OK, I think I have solved the problem by a better method, I add one line in `parse_base` class: `using charType = T;`, now try to extract the actual `charType`, I just use the `typename` statement, such as: `template struct seq_ final : public parser_base, typename Left::charType> {`. I just see it works, but C++ template is still a magic place for me. – ollydbg23 Feb 07 '23 at 03:37