我想使用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
}
如果任何子规则不成功,是否有办法使规则失败?
答案 0 :(得分:3)
我不知道您使用的是什么版本的提升,但是
1_49_0没有失败并打印:
123
123;456
1_54_0没有失败并打印:
123
123;456
所以,我无法重现这个问题。但是,从概念上讲,我认为您正在寻找 karma::buffer[]
:
临时输出缓冲的生成器指令(
buffer[]
)所有生成器组件(Alternative(|)生成器除外)将其生成的输出直接传递给基础输出流。如果生成器中途失败, 到目前为止生成的输出不会“回滚” 。缓冲生成器指令允许生成此不需要的输出。它暂时将嵌入式生成器生成的输出重定向到缓冲区。只有在嵌入式生成器成功 之后,此缓冲区 才会刷新到基础流,否则将被丢弃。
所以你可以添加
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
不能很好地混合,因为子表达式中对临时表的陈旧引用
答案 1 :(得分:2)
这是一个可行的解决方案,它创建一个名为full
的自定义指令(严重基于解释为here,full code的指令),只有当其主题返回true且数字返回时才返回true生成的元素数等于作为属性传递的容器中的元素数。
我所做的改变是:
columns_delimiter
替换为element_counter_delimiter
。simple_columns_generator
替换为full_container_generator
。columns
替换为full
。final_delimit_out
。generate
和element_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);
}