【问题标题】:Spirit.X3 using string_view and member named 'insert' compiler errorSpirit.X3 使用 string_view 和名为“插入”的成员编译器错误
【发布时间】:2018-05-12 19:34:48
【问题描述】:

在 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 as<> "directive",也请参见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; }
    }

据我所知,问题始于 boost 1.65。发生了什么变化以及如何解决?

最后,我有一个关于sehe提到的连续存储要求的问题:我理解这个要求,但是解析器可以违反这个吗? - 在我看来,解析器即使在回溯时也必须失败,所以这不可能发生在精神上。通过使用error_handler,引用string_view的内存存储地址最终在解析级别有效。我的结论是,在这种情况下,只要引用在范围内,使用 string_view 就可以保存,不是吗?

【问题讨论】:

    标签: c++ boost boost-spirit-x3


    【解决方案1】:

    这里的问题似乎与 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>;
    

    在 Qi 中,这本来是可特化的:

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

    但是在 X3 中它开始是一个模板别名,不能专门化。

    这是一个棘手的问题,因为似乎根本没有自定义点可以让 X3 做我们需要做的事情。

    解决方法

    我试图深入挖掘。我还没有看到解决这个问题的“干净”方式。事实上,属性强制技巧可以提供帮助,但是,如果您使用它来“缩短”导致匹配的启发式:

    • 该属性“类似于”“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);
    

    要真正坚持下去,我们需要专业化。但是,专业化和功能模板is not a good match。特别是,我们不能部分专门化,所以我们最终会对模板参数进行硬编码:

    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&lt;&gt;,它似乎不是为扩展而设计的。

    我确实意识到我自己过去曾应用过它,但我也边走边学。

    强制:入侵系统

    与其声明规则的属性 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
    

    另类

    这似乎很脆弱。例如。如果你change Iterator to char const*(或use std::string constinput = "hello_world_",但不是both),它会中断。

    这是一个更好的选择(我认为):

    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)

    最终版本上线

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

    【讨论】:

    • 规则声明提到迭代器类型是一种负担,因为我使用 x3 示例所示的样式实现了 BNF,例如calc9,但有约 300 条规则。我必须将迭代器类型提升到顶级规则。一个解决方案是使用 typedef e.g. from parser config 并参数化 string_view 规则。否则,string_view 的这个问题会不时出现,直到 string_view 成为 x3 的第一个公民成员,例如std::vector。最好的可能是按原样使用 x3::raw。
    • ... 并将 iterator_range 与稍后实例化的迭代器一起使用。不管怎样,你的回答解决了问题!
    • 哦,是的,很遗憾,您可以使用足够的模板元编程来摆脱困境。但是,现在我认为我在“你也可以隐藏这个:”下显示的快速修复已经实现了“不要重复自己”的目标。迭代器类型。
    • 我同意在 x3 中对 string_view 的属性转发存在问题需要解决,如果您可以将“整洁”的解决方案推迟到这样的时间,那可能是明智的。请考虑在邮件列表中提交问题或github.com/boostorg/spirit
    猜你喜欢
    • 1970-01-01
    • 2020-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-22
    相关资源
    最近更新 更多