c++templatesc++20string-viewstd-span

what is the way to remove the first element from a std::span<T>?


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.


Solution

  • 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.