1

我想使用 Boost Spirit Karma 输出一个字符串向量。如果任何字符串不满足约束,则输出生成应该失败。我尝试了以下方法:

#include <boost/spirit/include/karma.hpp>

namespace ka = boost::spirit::karma;

int main()
{
    typedef std::ostream_iterator<char> iterator_t;

    std::string is1{"123"}, is2{"def"};
    std::vector<std::string> iv1{"123", "456"}, iv2{"123","def"};

    auto num = +ka::char_("0-9");
    auto nums = num % ka::lit(";");

    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is1) == true);
    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is2) == false);

    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv1) == true);
    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv2) == false); // Assertion Fails
}

如果任何子规则不成功,有没有办法使规则失败?

4

2 回答 2

3

我不知道你用的是什么版本的 boost,但是

  • 1_42_0 不编译您的代码段
  • 1_49_0 不会失败并打印:

    123
    123;456
    
  • 1_54_0 不会失败并打印:

    123
    123;456
    

所以,我无法重现这个问题。但是,从概念上讲,我认为您正在寻找karma::buffer[]

用于临时输出缓冲的生成器指令 ( buffer[])

所有生成器组件(替代 (|) 生成器除外)将其生成的输出直接传递到底层输出流。如果生成器在中途发生故障,则到目前为止生成的输出不会“回滚”。缓冲生成器指令允许避免生成这种不需要的输出。它临时将嵌入式生成器产生的输出重定向到缓冲区。仅在嵌入式生成器成功后,此缓冲区才会刷新到底层流,否则将被丢弃。

所以你可以添加

ka::rule<iterator_t, std::string()>              num  = +ka::char_("0-9");
ka::rule<iterator_t, std::vector<std::string>()> nums = ka::buffer [ num % ka::lit(";") ];

请注意,我不排除您正在查看未定义行为,因为 Proto 表达式树不能很好地混合,auto因为子表达式中对临时对象的引用过时。

于 2013-10-17T08:42:09.983 回答
2

这是一种可能的解决方案,它创建了一个自定义指令(很大程度上基于此处解释的完整代码) ,该指令full仅在其主题返回 true 并且生成的元素数等于传递为的容器中的元素数时才返回 true属性。

我所做的更改是:

  • 替换columns_delimiterelement_counter_delimiter.
  • 替换simple_columns_generatorfull_container_generator.
  • 替换columnsfull.
  • 删除了成员函数final_delimit_out
  • generateelement_counter_delimiter和 中稍作修改full_container_generator
  • 添加adjust_size以说明%生成2*num_elem - 1时间的事实(n 个整数和 n-1 个分号)

现场示例

#include <iostream>
#include <boost/spirit/include/karma.hpp>

//START OF FULL.HPP
#include <boost/spirit/include/karma_generate.hpp>

///////////////////////////////////////////////////////////////////////////////
// definition the place holder 
namespace custom_generator 
{ 
    BOOST_SPIRIT_TERMINAL(full);
} 

///////////////////////////////////////////////////////////////////////////////
// implementation the enabler
namespace boost { namespace spirit 
{ 
    // We want custom_generator::full to be usable as a directive only, 
    // and only for generator expressions (karma::domain).
    template <>
    struct use_directive<karma::domain, custom_generator::tag::full> 
      : mpl::true_ {}; 
}}

///////////////////////////////////////////////////////////////////////////////
// implementation of the generator
namespace custom_generator
{ 
    template <typename T>
    struct adjust_size
    {
        static std::size_t call(std::size_t val)
        {
            return val; //with kleene and repeat just return the value
        }
    };

    template <typename Left, typename Right>
    struct adjust_size<boost::spirit::karma::list<Left,Right> >
    {
        static std::size_t call(std::size_t val)
        {
            return (val+1)/2; //with list you output n elements and n-1 semicolons
        }
    };
    // special delimiter wrapping the original one that counts the number of elements
    template <typename Delimiter>
    struct element_counter_delimiter 
    {
        element_counter_delimiter(Delimiter const& delim)
          : delimiter(delim), count(0) {}

        // This function is called during the actual delimiter output 
        template <typename OutputIterator, typename Context
          , typename Delimiter_, typename Attribute>
        bool generate(OutputIterator& sink, Context&, Delimiter_ const&
          , Attribute const&) const
        {
            // first invoke the wrapped delimiter
            if (!boost::spirit::karma::delimit_out(sink, delimiter))
                return false;

            // now we count the number of invocations 
            ++count;

            return true;
        }

        Delimiter const& delimiter;   // wrapped delimiter
        mutable unsigned int count;   // invocation counter
    };

    // That's the actual full generator
    template <typename Subject>
    struct full_container_generator
      : boost::spirit::karma::unary_generator<
            full_container_generator<Subject> >
    {
        // Define required output iterator properties
        typedef typename Subject::properties properties;

        // Define the attribute type exposed by this parser component
        template <typename Context, typename Iterator>
        struct attribute 
          : boost::spirit::traits::attribute_of<Subject, Context, Iterator> 
        {};

        full_container_generator(Subject const& s)
          : subject(s)
        {}

        // This function is called during the actual output generation process.
        // It dispatches to the embedded generator while supplying a new 
        // delimiter to use, wrapping the outer delimiter.
        template <typename OutputIterator, typename Context
          , typename Delimiter, typename Attribute>
        bool generate(OutputIterator& sink, Context& ctx
          , Delimiter const& delimiter, Attribute const& attr) const
        {
            std::size_t elems_in_container = boost::spirit::traits::size(attr);
            element_counter_delimiter<Delimiter> d(delimiter);
            if (!subject.generate(sink, ctx, d, attr))
                return false;
            return elems_in_container == adjust_size<Subject>::call(d.count);
        }

        // This function is called during error handling to create
        // a human readable string for the error context.
        template <typename Context>
        boost::spirit::info what(Context& ctx) const
        {
            return boost::spirit::info("full", subject.what(ctx));
        }

        Subject subject;
    };
}

///////////////////////////////////////////////////////////////////////////////
// instantiation of the generator
namespace boost { namespace spirit { namespace karma
{
    // This is the factory function object invoked in order to create 
    // an instance of our full_container_generator.
    template <typename Subject, typename Modifiers>
    struct make_directive<custom_generator::tag::full, Subject, Modifiers>
    {
        typedef custom_generator::full_container_generator<Subject> result_type;

        result_type operator()(unused_type, Subject const& s, unused_type) const
        {
            return result_type(s);
        }
    };
}}}

//END OF FULL.HPP


int main()
{
    typedef std::ostream_iterator<char> iterator_t;
    namespace ka=boost::spirit::karma;

    std::string is1{"123"}, is2{"def"};
    std::vector<std::string> iv1{"123", "456"}, iv2{"123","def"}, iv3{"123", "456", "789"}, iv4{"123", "456", "def"};

    using custom_generator::full;

    ka::rule<iterator_t,std::string()> num = +ka::char_("0-9"); //this rule needs to have attribute std::string
                                                                //that wasn't the case with the original "auto num =..."
                                                                //and it caused that the delimiter count went way higher than it should
    ka::rule<iterator_t,std::vector<std::string>()> nums = full[num%ka::lit(";")];


    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is1) == true);
    assert(ka::generate(iterator_t{std::cout}, num << ka::eol, is2) == false);

    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv1) == true);
    assert(ka::generate(iterator_t{std::cout}, ka::buffer[nums << ka::eol], iv2) == false); //using buffer as mentioned by sehe
    assert(ka::generate(iterator_t{std::cout}, nums << ka::eol, iv3) == true);
    assert(ka::generate(iterator_t{std::cout}, ka::buffer[nums << ka::eol], iv4) == false);
}
于 2013-10-17T19:11:45.973 回答