boost :: spirit :: karma替代生成器,带有boost :: variant,由字符串和字符串别名组成

时间:2018-10-25 11:49:12

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

我有一个boost::variant,它由几种类型组成,包括字符串类型别名和字符串类型。字符串类型别名与boost::spirit::qi替代解析器一样工作,但是boost::spirit::karma替代生成器不仅不使用不需要的字符串类型别名生成器规则,而且以不想要的方式而且是意外的方式工作,但是当变体包含字符串类型时,甚至不使用内置的字符串生成器:

#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>

using mode = std::string;
using alt_variant = boost::variant<mode, std::string, unsigned>;
using alt_variant_without_string = boost::variant<mode, unsigned>;

template <typename OutputIterator>
boost::spirit::karma::rule<OutputIterator, mode()>
        mode_gen{
        boost::spirit::karma::lit("mode=\"") <<
                                             boost::spirit::karma::string
                                             << boost::spirit::karma::lit("\"")
};

int main(int argc, char *argv[]) {
    alt_variant foo1{mode{"bar"}};
    alt_variant_without_string foo2{mode{"bar"}};
    std::string output;
    using namespace boost::spirit::karma;
    const auto gen = mode_gen<std::back_insert_iterator<std::string>> | uint_ | string;

    boost::spirit::karma::generate(std::back_inserter(output), gen, foo1);
    std::cout << "Output\"" << output << "\"\n"; //Output""

    output.clear();

    boost::spirit::karma::generate(std::back_inserter(output), gen, foo2);
    std::cout << "Output\"" << output << "\"\n";//Output"mode="bar""

    return 0;
}

有人可以解释这种行为以及我如何获得想要的行为吗?

我猜对于下一个,我必须摆脱所有字符串类型的别名,并使用显式结构作为类型,但随后我又陷入了一个丑陋的成员结构的困境。 (https://codereview.stackexchange.com/q/206259/95143 但是,第一个输出不只是“ bar”,即当模式生成器也不存在时不使用字符串生成器,这对我来说似乎是个错误,即我无法理解。

1 个答案:

答案 0 :(得分:3)

从哪里开始。

A。未指明的行为

  

这实际上可能是Undefined Behaviour,但我没有查看文档。

类型别名不会创建新类型。因此,typeid(std::string) == typeid(mode)并无法通过变体来区分这两种元素类型。

未指定Variant的行为。比较: Live On Coliru

boost::variant<mode, std::string> v;

Live On Coliru

boost::variant<int, mode, std::string> v;

B。未定义的行为

然后你做

const auto gen = mode_gen<std::back_insert_iterator<std::string> > | uint_ | string;

与Qi一样适用:原型表达式通过引用保存规则操作数,这意味着auto是一个坏主意:

使用UBSan / ASan运行代码,并使用Valgring捕获此类错误,然后再吞噬客户的数据。

问题

您的问题是您需要可以打开的表达类型。我认为Java主义者喜欢将其称为“抽象数据类型”。这是一个崇高的目标,您可以:

解决方案1 ​​

使mode为自定义类型:

Live On Coliru

#include <boost/spirit/include/karma.hpp>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>

struct mode : std::string {
    using std::string::string;
};

namespace karma = boost::spirit::karma;

template <typename Out = boost::spirit::ostream_iterator>
karma::rule<Out, mode()> mode_gen = "mode=\"" << karma::string << "\"";

int main() {
    using Variant = boost::variant<mode, std::string, unsigned>;

    Variant foo = std::string("foo"),
            bar = mode("bar"),
            i = 42;

    for (Variant v : { foo, bar, i })
        std::cout << "Output: " << format(mode_gen<> | karma::uint_ | karma::string, v) << "\n";
}

打印

Output: foo
Output: mode="bar"
Output: 42

解决方案2:强类型定义

我无法立即完成这项工作,所以让我只指出一个示例实现:

#include <boost/serialization/strong_typedef.hpp>

解决方案3:区分std::string

您可以使用hack:

namespace hack {
    template <typename Char, typename Tag>
    struct my_traits : std::char_traits<Char> {};
}

using mode = std::basic_string<char, hack::my_traits<char, struct ModeTag> >;

仍然打印相同的 Live On Coliru

Output: foo
Output: mode="bar"
Output: 42

奖金

生成器存在问题。具体来说,如果您的mode值包含引号,则情况会出错。您可能只是利用ostream

struct mode : std::string {
    using std::string::string;

    friend std::ostream& operator<<(std::ostream& os, mode const& m) {
        return os << "mode=" << std::quoted(m);
    }
};

这样简单

std::cout << mode("yo") << std::endl;
std::cout << mode("y\"!\"o") << std::endl;

将打印 Live On Coliru

mode="yo"
mode="y\"!\"o"

哪个更优雅。这也意味着您可以将所有业力语法替换为karma::stream

Live On Coliru

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

struct mode : std::string {
    using std::string::string;

    friend std::ostream& operator<<(std::ostream& os, mode const& m) {
        return os << "mode=" << std::quoted(m);
    }
};

int main() {
    boost::variant<mode, std::string, unsigned> 
        foo = std::string("foo"),
        bar = mode("bar"),
        i = 42;

    for (auto v : { foo, bar, i })
        std::cout << "Output: " << karma::format(karma::stream, v) << "\n";
}
  

当越来越少的代码完成越来越多的工作时,我会喜欢它。但是以这种速度,有人想知道为什么甚至使用karma

Bonus#2-ADL它,谁需要业力

要通过my_traits方法和您的代码类型使它大放异彩,请最大程度地考虑参数依赖查找:

Live On Coliru

#include <boost/variant.hpp>
#include <iostream>
#include <iomanip>

namespace hack {
    template <typename Char, typename Tag>
    struct my_traits : std::char_traits<Char> {};
}

namespace mylib {
    struct ModeTag{};
    struct ValueTag{};

    static inline std::ostream& operator<<(std::ostream& os, ModeTag)  { return os << "mode"; }
    static inline std::ostream& operator<<(std::ostream& os, ValueTag) { return os << "value"; }

    template <typename Char, typename Tag>
    static inline std::ostream& operator<<(std::ostream& os, hack::my_traits<Char, Tag>)
        { return os << Tag{}; }

    template <typename Char, typename CharT, typename Alloc>
    std::ostream& operator<<(std::ostream& os, std::basic_string<Char, CharT, Alloc> const& s) {
        return os << CharT{} << "=" << std::quoted(s);
    }
}

using mode = std::basic_string<char, hack::my_traits<char, struct mylib::ModeTag> >;
using value = std::basic_string<char, hack::my_traits<char, struct mylib::ValueTag> >;

int main() {
    boost::variant<mode, value, unsigned> 
        foo = value("foo"),
        bar = mode("bar"),
        i = 42;

    std::cout << foo << std::endl;
    std::cout << bar << std::endl;
    std::cout << i << std::endl;
}

它的编译速度提高了10倍并可以打印:

value="foo"
mode="bar"
42