Spirit.X3, использующий string_view и член с именем 'insert', ошибка компилятора - PullRequest
0 голосов
/ 12 мая 2018

В Stackoverflow есть несколько Вопросов и Ответов, связанных с использованием {boost, std}::string_view, например:

llonesmiz написал пример на wandbox , который компилируется с boost 1.64 , ноне удалось с boost 1.67 , теперь с

    opt/wandbox/boost-1.67.0/gcc-7.3.0/include/boost/spirit/home/x3/support/traits/container_traits.hpp:177:15: error: 'class boost::basic_string_view<char, std::char_traits<char> >' has no member named 'insert'
                 c.insert(c.end(), first, last);
                 ~~^~~~~~

с той же ошибкой, с которой я столкнулся в своем проекте.

Проблема возникает также при использовании std::string даже при явном использовании Sehe's в качестве <> "директивы" , см. Также в wandbox :

    #include <iostream>
    #include <string>
    #include <string_view>

    namespace boost { namespace spirit { namespace x3 { namespace traits {

    template <typename It>
    void move_to(It b, It e, std::string_view& v)
    {
        v = std::string_view(&*b, e-b);
    }

    } } } } // namespace boost


    #include <boost/spirit/home/x3.hpp>


    namespace boost { namespace spirit { namespace x3 { namespace traits {

    template <>
    struct is_substitute<raw_attribute_type, std::string_view> : boost::mpl::true_
    {};

    } } } } // namespace boost


    namespace parser
    {
        namespace x3 = boost::spirit::x3;
        using x3::char_;
        using x3::raw;

        template<typename T>
        auto as = [](auto p) { return x3::rule<struct _, T>{ "as" } = x3::as_parser(p); };

        const auto str = as<std::string_view>(raw[ +~char_('_')] >> '_');
        const auto str_vec  = *str;
    }

    int main()
    {
        std::string input = "hello_world_";

        std::vector<std::string_view> strVec; 
        boost::spirit::x3::parse(input.data(), input.data()+input.size(), parser::str_vec, strVec);

        for(auto& x : strVec) { std::cout << x << std::endl; }
    }

Насколько я видел, проблема начинается с повышения 1.65.Что было изменено и как это исправить?

Наконец, у меня есть вопрос о требованиях к непрерывному хранилищу, упомянутый sehe : я понимаю требования этого, но анализатор понимаетможет нарушить это?- На мой взгляд, парсер должен потерпеть неудачу даже при возврате, так что это не может произойти по духу.Используя обработчик ошибок, адрес хранения в памяти, который ссылается на string_view, наконец-то на уровне синтаксического анализа является действительным.Я пришел к выводу, что сохранить string_view можно, поскольку ссылки находятся в области видимости при этом условии, не так ли?

1 Ответ

0 голосов
/ 13 мая 2018

Проблема здесь, похоже, заключается в черте is_container:

template <typename T>
using is_container = mpl::bool_<
    detail::has_type_value_type<T>::value &&
    detail::has_type_iterator<T>::value &&
    detail::has_type_size_type<T>::value &&
    detail::has_type_reference<T>::value>;

В Ци это было бы специализировано:

template <> struct is_container<std::string_view> : std::false_type {};

Однако в X3 это сталопсевдоним шаблона, который не может быть специализированным.

Это сложная проблема, поскольку кажется, что просто нет точки настройки, чтобы заставить X3 делать то, что нам нужно здесь.

Обходное решение

Я пытался копать глубже.Я не видел "чистого" способа обойти это.Фактически, трюк приведения атрибута может помочь, хотя, если вы используете его, чтобы "замкнуть" эвристику, которая вызывает совпадение:

  • атрибут "как a"контейнер "char"
  • , парсер может соответствовать такому контейнеру

В этой ситуации мы можем принудительно установить атрибут парсера в , в частности , как несовместимый, ивсе начнет работать.

Правильное переопределение move_to

Это тоже предмет спора.Просто добавление перегрузки типа:

template <typename It>
inline void move_to(It b, It e, std::string_view& v) {
    v = std::string_view(&*b, std::distance(b,e));
}

недостаточно, чтобы сделать его наилучшей перегрузкой.

Базовый шаблон

template <typename Iterator, typename Dest>
inline void move_to(Iterator first, Iterator last, Dest& dest);

Чтобы на самом деле сделать это, нам нужно специализировать .Однако специализированные и функциональные шаблоны не очень хорошо подходят .В частности, мы не можем частично специализироваться, поэтому в итоге мы жестко закодируем аргументы шаблона:

template <>
inline void move_to<Iterator, std::string_view>(Iterator b, Iterator e, std::string_view& v) {
    v = std::string_view(&*b, std::distance(b,e));
}

Это вызывает у меня вопрос, является ли move_to "обслуживаемым пользователем"вообще, как и is_container<> выше, кажется, что он просто не предназначен для расширения.

Я понимаю, что сам применял это в прошлом, но я также учусь на ходу.

Принуждение: взлом системы

Вместо того, чтобы объявлять атрибут правила std::string_view (оставляя магическую комнату типа X3, чтобы "сделать вещь правильная "), давайте вытравим каменьожидаемый результат raw[] (и оставьте X3, чтобы сделать остальную магию, используя move_to):

namespace parser {
    namespace x3 = boost::spirit::x3;
    const auto str 
        = x3::rule<struct _, boost::iterator_range<Iterator> >{"str"}
        = x3::raw[ +~x3::char_('_')] >> '_';
    const auto str_vec  = *str;
}

Это работает.Посмотреть Live On Wandbox

Печать

hello
world

Альтернатива

Это кажется хрупким.Например, он сломается, если вы измените Iterator на char const* (или используйте std::string const input = "hello_world_", но не оба ).

Вот лучший вариант (я думаю):

namespace boost { namespace spirit { namespace x3 {

    template <typename Char, typename CharT, typename Iterator> 
    struct default_transform_attribute<std::basic_string_view<Char, CharT>, boost::iterator_range<Iterator>> {
        using type = boost::iterator_range<Iterator>;

        template <typename T> static type pre(T&&) { return {}; }

        static void post(std::basic_string_view<Char, CharT>& sv, boost::iterator_range<Iterator> const& r) {
            sv = std::basic_string_view<Char, CharT>(std::addressof(*r.begin()), r.size());
        }
    };

} } }

Теперь, единственное, что осталось сделать, это то, что в объявлении правила упоминается тип итератора.Вы также можете скрыть это:

namespace parser {
    namespace x3 = boost::spirit::x3;

    template <typename It> const auto str_vec = [] {
        const auto str 
            = x3::rule<struct _, boost::iterator_range<It> >{"str"}
            = x3::raw[ +~x3::char_('_')] >> '_';
        return *str;
    }();
}

auto parse(std::string_view input) {
    auto b = input.begin(), e = input.end();
    std::vector<std::string_view> data;
    parse(b, e, parser::str_vec<decltype(b)>, data);
    return data;
}

int main() {
    for(auto& x : parse("hello_world_"))
        std::cout << x << "\n";
}

Это сразу показывает, что он работает с итераторами без указателей.

Примечание: для полноты картины вы хотите статически утверждать модели итераторов концепцию ContiguousIterator (c ++ 17)

Final Version Live

Live On Wandbox

#include <iostream>
#include <string>
#include <string_view>
#include <boost/spirit/home/x3.hpp>

namespace boost { namespace spirit { namespace x3 {

    template <typename Char, typename CharT, typename Iterator> 
    struct default_transform_attribute<std::basic_string_view<Char, CharT>, boost::iterator_range<Iterator>> {
        using type = boost::iterator_range<Iterator>;

        template <typename T> static type pre(T&&) { return {}; }

        static void post(std::basic_string_view<Char, CharT>& sv, boost::iterator_range<Iterator> const& r) {
            sv = std::basic_string_view<Char, CharT>(std::addressof(*r.begin()), r.size());
        }
    };

} } }

namespace parser {
    namespace x3 = boost::spirit::x3;

    template <typename It> const auto str_vec = [] {
        const auto str 
            = x3::rule<struct _, boost::iterator_range<It> >{"str"}
            = x3::raw[ +~x3::char_('_')] >> '_';
        return *str;
    }();
}

auto parse(std::string_view input) {
    auto b = input.begin(), e = input.end();
    std::vector<std::string_view> data;
    parse(b, e, parser::str_vec<decltype(b)>, data);
    return data;
}

int main() {
    for(auto& x : parse("hello_world_"))
        std::cout << x << "\n";
}
...