【问题标题】:Boost Karma: writing a rule with boost::optional<boost::variant<...>>Boost Karma:使用 boost::optional<boost::variant<...>> 编写规则
【发布时间】:2018-02-08 16:37:35
【问题描述】:

我正在尝试在 Karma 生成器中的 boost::optional 中使用 boost::variant。我已经能够将问题简化为:

using FooVariant = boost::variant<int>;
using FooOptional = boost::optional<FooVariant>;

template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
    : boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
    FooGenerator()
        : FooGenerator::base_type(start_)
    {
        namespace bsk = boost::spirit::karma;
        start_ = '[' << ( bsk::int_ | '*' ) << ']';
    }

    boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};

int main()
{
    FooOptional fop1;
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fop1) << std::endl;

    FooOptional fop2 = FooVariant{123};
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fop2) << std::endl;
}

这里的输出是:

[*]
[*]

这不是我所希望的。

我更改的第一件事是通过将FooVariant 更改为using FooVariant = int; 或多或少地进行完整性检查。这输出:

[*]
[123]

这就是我想看到的!所以在我的第一个代码中,变体只有一种类型,所以我尝试添加第二种类型,只是为了看看:

using FooVariant = boost::variant<int, double>;
...
start_ = '[' << ( ( bsk::int_ | bsk::double_ ) | '*' ) << ']';

但是我们又回到了:

[*]
[*]

然后我尝试为该变体添加一个专门的规则:

using FooVariant = boost::variant<int, double>;
using FooOptional = boost::optional<FooVariant>;

template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
    : boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
    FooGenerator()
        : FooGenerator::base_type(start_)
    {
        namespace bsk = boost::spirit::karma;
        foovar_ = (bsk::int_ | bsk::double_);
        start_ = '[' << ( foovar_ | '*' ) << ']';
    }

    boost::spirit::karma::rule<OutputIt, FooVariant()> foovar_;
    boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};

int main()
{
    FooOptional fop1;
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fop1) << std::endl;

    FooOptional fop2 = FooVariant{123};
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fop2) << std::endl;
}

这会导致编译错误:

alternative_function.hpp:127:34: error: no member named 'is_compatible' in
      'boost::spirit::traits::compute_compatible_component<boost::variant<int, double>, boost::optional<boost::variant<int, double> >, boost::spirit::karma::domain>'
            if (!component_type::is_compatible(spirit::traits::which(attr_)))
                 ~~~~~~~~~~~~~~~~^

看起来模板生成试图确保boost::variant 和boost::optional 兼容,但对我来说问题是“为什么要确保它们完全兼容?”

我怎样才能做到这一点?

【问题讨论】:

  • 两件事。 1. 我不禁注意到您仍然忽略了微小的位/只是/使您的代码不是自包含的(这里没有多少人知道要包含哪些标头)。另外,2. 我看到你问了很多问题,也许你可以查看你收到的答案并投票/接受那些值得的答案。
  • 我将在以后的问题中包含标题。我总是赞成!

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


【解决方案1】:

我在另一个答案中告诉过你我将如何处理这个问题:Boost Karma: generate default text when boost::optional is unset

这不仅回避了这个问题,还简化了 AST/数据类型。

现在,既然你强迫我¹,我就挖了。问题出在foovar_ | '|' 是一个 alternative-expression 的事实,它以某种方式验证了该属性必须是兼容的。在该检查期间,假设

  • 如果a 具有属性attr(a) 并且b 具有属性attr(b) 则
  • a | b 应该有 variant&lt;attr(a), attr(b)

它确实做了一些逻辑检查(比如当attr(a) == attr(b)),但不是逻辑检查,如果attr(b)是unused_type,那么它也应该与optional&lt;attr(a)&gt;兼容,而不仅仅是variant&lt;attr(a), unused_type&gt;甚至@ 987654336@.

现在,为什么我不那么感兴趣²。所以,这里是你可以强制事情的方法。

    foovar_ = bsk::int_ | bsk::double_;
    fooopt_ = (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_;
    start_ = '[' << fooopt_ << ']';

key 是将其编码为不同于alternative-expression 的东西。在这里,我们只是相信-foovar_ 可以说或不说。另外,我们也做

bsk::eps(is_initialized_(bsk::_val)) | '*'

含义:如果变体被初始化,就这样,否则,生成'*'。

现在你并不严格需要第三条规则,如果你喜欢只写代码,你可以写:

    start_ = '[' << (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_ << ']';

演示

哦,我差点忘了:

struct is_initialized_f {
    template<typename T>
    bool operator()(boost::optional<T> const& opt) const { return opt.is_initialized(); }
};
boost::phoenix::function<is_initialized_f> is_initialized_;

实现帮助 Phoenix actor 检查初始化状态。

Live On Coliru

#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
using FooVariant = boost::variant<int, double>;
using FooOptional = boost::optional<FooVariant>;

template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
    : boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
    FooGenerator()
        : FooGenerator::base_type(start_)
    {
        namespace bsk = boost::spirit::karma;
        namespace phx = boost::phoenix;

        foovar_ = bsk::int_ | bsk::double_;
        //fooopt_ = (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_;
        start_ = '[' << (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_ << ']';
    }

  private:
    struct is_initialized_f {
        template<typename T>
        bool operator()(boost::optional<T> const& opt) const { return opt.is_initialized(); }
    };
    boost::phoenix::function<is_initialized_f> is_initialized_;
    boost::spirit::karma::rule<OutputIt, FooVariant()> foovar_;
  //boost::spirit::karma::rule<OutputIt, FooOptional()> fooopt_;
    boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};

int main()
{
    for (FooOptional fop : { FooOptional{}, {FooVariant{123}}, {FooVariant{3.14}} }) {
        if (std::cout << boost::spirit::karma::format(FooGenerator<>(), fop))
            std::cout << "\n";
        else
        {
            std::cout.clear();
            std::cout << "#Error\n";
        }
    }
}

打印

[*]
[123]
[3.14]

¹笑话

² 我们会得到与“有人忘记”或“这是一种回归”押韵的非权威答案

【讨论】:

  • PS “为什么要确保它们完全兼容?” 这是我确实感兴趣的唯一“原因”,答案是:因为 C++ 是一种强类型语言,如果类型不兼容,事情将无法编译(这不是魔术:它都在属性传播逻辑中明确编程)
  • 我了解 C++ 是一种强类型语言,我的问题更多是为什么要比较这两种类型?我编写规则的方式似乎当时不应该比较 optional 和 variant。但是您在主要答案中回答了这个问题。
  • 请注意,上述解决方案仅适用于 boost 1.56 及更高版本(除非您专门研究 attribute_size 特征)。看到这个补丁github.com/boostorg/spirit/commit/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-01
  • 2018-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多