Переход анализатора Boost Spirit от boost :: варианта к стандартному варианту - PullRequest
1 голос
/ 23 апреля 2020

В настоящее время я пытаюсь отодвинуть некоторый код от использования boost :: variable в пользу std :: variable, но столкнулся с проблемой, которую не могу понять. Ниже приведен минимальный тестовый пример:

#include <string>
#include <variant>

#include <boost/spirit/home/x3.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

struct Recurse;
//using Base = boost::variant< // This works
using Base = std::variant<
    std::string,
    boost::recursive_wrapper<Recurse>>;

struct Recurse
{
    int _i;
    Base _base = std::string{};
};

BOOST_FUSION_ADAPT_STRUCT(
    Recurse,
    (int, _i),
    (Base, _base)
)

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;

BOOST_SPIRIT_DEFINE(base)

int main()
{
    std::string text;
    Base result;
    x3::phrase_parse(std::begin(text), std::end(text), base, ascii::space, result);
    return 0;
}

Wandbox для ошибки

Что Я думаю, что происходит в том, что парсер пытается присваивать int напрямую значению типа Base, но, поскольку int напрямую не отображается на std :: string или boost :: recursive_wrapper <>, он расстраивается (из-за этого я имею в виду 11 страниц ошибок компилятора ). Каким-то образом , boost :: variable позволяет избежать этой проблемы. Любые подсказки, пожалуйста?

1 Ответ

2 голосов
/ 24 апреля 2020

Почему-то boost::variant избегает ошибки.

Да. Вариант Boost имеет поддержку распространения атрибутов.

Кроме того, boost::variant имеет специальную обработку boost::recursive_wrapper, так что это может быть двойной запретный полет.

Хорошая статья о рекурсии std::variant s здесь https://vittorioromeo.info/index/blog/variants_lambdas_part_2.html

Что не так с boost::variant?

Если вы хотите, вы можете написать некоторые черты трансформации или даже посмотреть на x3 :: вариант - он может подойти вам лучше?

Live On Coliru

#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

struct Recurse;
using Base = x3::variant<
    std::string,
    x3::forward_ast<Recurse> >;

struct Recurse
{
    int _i;
    Base _base;
};

BOOST_FUSION_ADAPT_STRUCT(
    Recurse,
    (int, _i),
    (Base, _base)
)

const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;

BOOST_SPIRIT_DEFINE(base)

int main()
{
    std::string text;
    Base result;
    x3::phrase_parse(std::begin(text), std::end(text), base, ascii::space, result);
    return 0;
}

Примечание: нет x3::forward_ast<> не помогает с std::variant, подтверждая, что std::variant просто не хватает поддержки в x3

ОБНОВЛЕНИЕ

Вы можете обойти все, сделав Base производной структурой с необходимым механизмом, чтобы указать Духу, что это вариант (и над какими типами). Таким образом, вам не нужно go через ад специализации черт:

struct Recurse;

struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
    using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
    using BaseV::BaseV;
    using BaseV::operator=;

    struct adapted_variant_tag {};
    using types = boost::mpl::list<std::string, Recurse>;
};

struct Recurse {
    int _i;
    Base _base;
};

Как вы можете видеть, это в основном то же самое, но добавляет adapted_variant_tag и types вложенных типов.

Обратите внимание , что, умело жестко закодировав последовательность types, мы можем притворно обращаться с рекурсивной оболочкой. Нам повезло, что этого достаточно, чтобы обмануть систему.

Добавление некоторых отладочных выходных данных и тестовых случаев:

Live On Coliru

#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

namespace { // for debug
    template<class T>
    std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
       return os << rw.get();
    }
    template<class... Ts>
    std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
       std::visit([&os](const auto& v) { os << v; }, sv);
       return os;
    }
}

struct Recurse;

struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
    using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
    using BaseV::BaseV;
    using BaseV::operator=;

    struct adapted_variant_tag {};
    using types = boost::mpl::list<std::string, Recurse>;
};

struct Recurse {
    int _i;
    Base _base;
    friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
        return os << "[" << r._i << ", " << r._base << "]";
    }
};

BOOST_FUSION_ADAPT_STRUCT(
    Recurse,
    (int, _i),
    (Base, _base)
)

static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;

BOOST_SPIRIT_DEFINE(base)

int main()
{
    for (std::string const text : { "yeah8", "32 more" }) {
        Base result;
        auto f = begin(text), l = end(text);
        if (x3::phrase_parse(f, l, base, ascii::space, result)) {
            std::cout << "Result: " << result << "\n";
        } else {
            std::cout << "Failed\n";
        }

        if (f!=l) {
            std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
        }

    }
}

Какие отпечатки

Result: yeah8
Result: [32, more]

Обновление 2: Обледенение торта

Вот черты, необходимые для того, чтобы std::variant просто работал:

namespace boost::spirit::x3::traits {
    template<typename... t>
    struct is_variant<std::variant<t...> >
        : mpl::true_ {};

    template <typename attribute, typename... t>
    struct variant_has_substitute_impl<std::variant<t...>, attribute>
    {
        typedef std::variant<t...> variant_type;
        typedef typename mpl::transform<
              mpl::list<t...>
            , unwrap_recursive<mpl::_1>
            >::type types;
        typedef typename mpl::end<types>::type end;

        typedef typename mpl::find<types, attribute>::type iter_1;

        typedef typename
            mpl::eval_if<
                is_same<iter_1, end>,
                mpl::find_if<types, traits::is_substitute<mpl::_1, attribute>>,
                mpl::identity<iter_1>
            >::type
        iter;

        typedef mpl::not_<is_same<iter, end>> type;
    };


    template <typename attribute, typename... t>
    struct variant_find_substitute<std::variant<t...>, attribute>
    {
        typedef std::variant<t...> variant_type;
        typedef typename mpl::transform<
              mpl::list<t...>
            , unwrap_recursive<mpl::_1>
            >::type types;

        typedef typename mpl::end<types>::type end;

        typedef typename mpl::find<types, attribute>::type iter_1;

        typedef typename
            mpl::eval_if<
                is_same<iter_1, end>,
                mpl::find_if<types, traits::is_substitute<mpl::_1, attribute> >,
                mpl::identity<iter_1>
            >::type
        iter;

        typedef typename
            mpl::eval_if<
                is_same<iter, end>,
                mpl::identity<attribute>,
                mpl::deref<iter>
            >::type
        type;
    };

    template <typename... t>
    struct variant_find_substitute<std::variant<t...>, std::variant<t...> >
        : mpl::identity<std::variant<t...> > {};
}

Это много шума, но вы можете убрать его где-нибудь в заголовке.

БОНУС

Исправление грамматики:

  • вы, вероятно, имели в виду чтобы иметь lexeme[] вокруг строки
  • вы, вероятно, хотели иметь минимальную длину строки (+ char_, а не * char_), видя, что нет разделителей
  • , которые могут иметься переупорядочить ветви, потому что производство строк сожрало бы целые числа для повторяющихся правил.

Вот мой исправленный взгляд на грамматику, где правила близко отражают AST, как обычно имеет смысл:

namespace Parser {
    static_assert(x3::traits::is_variant<Base>::value);
    const x3::rule<class Base_, Base> base = "base";
    const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
    const auto recurse = x3::int_ >> base;
    const auto base_def = recurse | string;
    BOOST_SPIRIT_DEFINE(base)
}

Упрощение Fusion

Последнее, но не менее важное, в эру C ++ 11 вы можете сделать вывод адаптированные члены Fusion:

BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)

Live Full Demo

Live On Coliru

#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

namespace { // for debug
    template<class T>
    std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
       return os << rw.get();
    }
    template<class... Ts>
    std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
       std::visit([&os](const auto& v) { os << v; }, sv);
       return os;
    }
}

struct Recurse;
using Base = std::variant<
    std::string,
    boost::recursive_wrapper<Recurse> >;

namespace boost::spirit::x3::traits {
    template<typename... T>
    struct is_variant<std::variant<T...> >
        : mpl::true_ {};

    template <typename Attribute, typename... T>
    struct variant_has_substitute_impl<std::variant<T...>, Attribute>
    {
        typedef std::variant<T...> variant_type;
        typedef typename mpl::transform<
              mpl::list<T...>
            , unwrap_recursive<mpl::_1>
            >::type types;
        typedef typename mpl::end<types>::type end;

        typedef typename mpl::find<types, Attribute>::type iter_1;

        typedef typename
            mpl::eval_if<
                is_same<iter_1, end>,
                mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute>>,
                mpl::identity<iter_1>
            >::type
        iter;

        typedef mpl::not_<is_same<iter, end>> type;
    };


    template <typename Attribute, typename... T>
    struct variant_find_substitute<std::variant<T...>, Attribute>
    {
        typedef std::variant<T...> variant_type;
        typedef typename mpl::transform<
              mpl::list<T...>
            , unwrap_recursive<mpl::_1>
            >::type types;

        typedef typename mpl::end<types>::type end;

        typedef typename mpl::find<types, Attribute>::type iter_1;

        typedef typename
            mpl::eval_if<
                is_same<iter_1, end>,
                mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute> >,
                mpl::identity<iter_1>
            >::type
        iter;

        typedef typename
            mpl::eval_if<
                is_same<iter, end>,
                mpl::identity<Attribute>,
                mpl::deref<iter>
            >::type
        type;
    };

    template <typename... T>
    struct variant_find_substitute<std::variant<T...>, std::variant<T...> >
        : mpl::identity<std::variant<T...> > {};
}

static_assert(x3::traits::is_variant<Base>{}, "");

struct Recurse
{
    int _i;
    Base _base;
    friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
        return os << "[" << r._i << ", " << r._base << "]";
    }
};

BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)

namespace Parser {
    static_assert(x3::traits::is_variant<Base>::value);
    const x3::rule<class Base_, Base> base = "base";
    const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
    const auto recurse = x3::int_ >> base;
    const auto base_def = recurse | string;
    BOOST_SPIRIT_DEFINE(base)
}

int main()
{
    for (std::string const text : { "yeah8", "32 more", "18 766 most" }) {
        Base result;
        auto f = begin(text), l = end(text);
        if (x3::phrase_parse(f, l, Parser::base, ascii::space, result)) {
            std::cout << "Result: " << result << "\n";
        } else {
            std::cout << "Failed\n";
        }

        if (f!=l) {
            std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
        }
    }
}

Какие отпечатки:

Result: yeah8
Result: [32, more]
Result: [18, [766, most]]

¹ (тонкая разница МОЖЕТ укусить вас в программировании generi c, где вам необходим явный доступ к базовому классу)

...