Проблема распространения атрибута Spirit Qi с однокомпонентной структурой

У меня есть проблема компиляции с Spirit Qi, где он жалуется, что value_type не является членом идентификатора. По какой-то причине система атрибутов Qi считает идентификатор типом контейнера и пытается перечислить его тип значения.

Это та же проблема, что и в этом вопросе, однако, я считаю, что причина - структура с одним членом и может быть связана с этой ошибкой.

#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

using namespace boost::spirit::qi; 

struct identifier
{
    std::wstring name;
};

struct problem
{
    identifier _1;
    identifier _2;
    identifier _3;
};

BOOST_FUSION_ADAPT_STRUCT(
    identifier,
    (std::wstring, name)
)

BOOST_FUSION_ADAPT_STRUCT(
    problem,
    (identifier, _1)
    (identifier, _2)
    (identifier, _3)
)



int main(int argc, char* argv[])
{
    rule<std::wstring::const_iterator, identifier()> gr_identifier = eps >> raw[lexeme[(alpha | '_') >> *(alnum | '_')]];

    // Ok, compiles
    /*rule<std::wstring::const_iterator, problem()> gr_problem =       gr_identifier
                                                                  >> gr_identifier
                                                                  >> '('
                                                                  >>   gr_identifier
                                                                  >>   ')';*/
    // Fails
    rule<std::wstring::const_iterator, problem()> gr_problem =       gr_identifier
                                                                  >> gr_identifier
                                                                  >> '('
                                                                  >   gr_identifier
                                                                  >   ')';

    std::wstring input = L"foo goo(hoo)";
    /*bool dummy = phrase_parse(
            input.begin(), input.end(), 
            gr_problem,
            space);*/

    return EXIT_SUCCESS;
}

Интересно, что это происходит только при использовании синтаксического анализатора ожидания (см. Определение 2 в примере). Определение 1, используя только синтаксический анализатор последовательности, правильно компилирует (и выполняет).

Кто-нибудь знает правильное решение этого?

Пожалуйста, посмотрите также пример из жизни.

1 ответ

Решение

Это довольно печально известный случай в духе. Проблема в том, что обработка специального случая одноэлементных последовательностей Fusion в Spirit нарушает некоторые абстракции.

Обычный обходной путь - адаптировать сторону открытых атрибутов, чтобы она была менее тривиальной:

rule<It, single_member_struct()> r = eps >> XXX; 
// the `eps` is there to break the spell

Однако здесь это не сработает, потому что ваш (a > XXX > b) Подвыражение приводит к другому vector1<decltype(member_type)> и на этот раз, никаких умных скобок или epsЯ спасу тебя.[1]

Короче говоря, у меня есть три обходных пути:


1. #define KEEP_STRING_WORKAROUND

Посмотри это в прямом эфире на Колиру

В котором вы просто позволите gr_identifier вернуть std::wstring[2]:

rule<It, std::string()> gr_identifier = 
    (alpha | '_') >> *(alnum | '_');

Это фактически просто откладывает преобразование магического атрибута, которое использует адаптацию Fusion, если identifierи тем самым разрушает заклинание:

rule<It, problem(), qi::space_type> gr_problem = 
       gr_identifier
    >> gr_identifier
    >> ('(' > gr_identifier > ')')
    ;

Просто работает. Я думаю, что это, вероятно, наименее навязчивый обходной путь


2. #define DUMMY_WORKAROUND

Посмотри это в прямом эфире на Колиру

В результате чего вы освобождаете магию, делая... identifier структура не слияния адаптирована к последовательности слияния одного элемента. Да. Это включает в себя EvilHack™ добавления фиктивного поля. Чтобы минимизировать путаницу, давайте сделаем это qi::unused_type хоть:

struct identifier
{
    std::string    name;
    qi::unused_type dummy;
};

BOOST_FUSION_ADAPT_STRUCT(
    identifier,
    (std::string,    name)
    (qi::unused_type, dummy)
)

И сейчас:

rule<It, identifier()> gr_identifier = 
    (alpha | '_') >> *(alnum | '_') >> attr(42);   // that's hacky

Работает


3. #define NO_ADAPT_WORKAROUND

Посмотри это в прямом эфире на Колиру

Окончательный обходной путь может быть наиболее очевидным: не адаптируйте структуру как последовательность слияния в первую очередь, а получайте прибыль:

struct identifier
{
    std::string name;

    identifier() = default;

    explicit identifier(std::string name) 
        : name(std::move(name))
    {}
};

Обратите внимание, что для разрешения распространения атрибутов теперь вам понадобятся подходящие конструкторы преобразования. Кроме того, конструктор по умолчанию требуется для открытых атрибутов в Spirit.

Сейчас,

rule<It, identifier()> gr_identifier = 
    as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion

работает. Это может быть более интуитивно понятно, если вам не нужен Fusion типа для других целей.

Примечание: этот вариант вполне может быть наиболее эффективным во время компиляции

Резюме

Я думаю, что для вашего кода есть 2 совершенно жизнеспособных обходных пути (#1 и #3) и один менее звездный (тот, что с пустым полем), но я включил его для документальных целей.

Полный код

Для дальнейшего использования

#define BOOST_SPIRIT_DEBUG
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace qi = boost::spirit::qi;

//////////////////////////////////////////
// Select workaround to demonstrate
#define KEEP_STRING_WORKAROUND
// #define DUMMY_WORKAROUND™
// #define NO_ADAPT_WORKAROUND
//////////////////////////////////////////

#if defined(KEEP_STRING_WORKAROUND)
    struct identifier
    {
        std::string    name;
    };

    BOOST_FUSION_ADAPT_STRUCT(
        identifier,
        (std::string,    name)
    )
#elif defined(DUMMY_WORKAROUND)
    struct identifier
    {
        std::string    name;
        qi::unused_type dummy;
    };

    BOOST_FUSION_ADAPT_STRUCT(
        identifier,
        (std::string,    name)
        (qi::unused_type, dummy)
    )
#elif defined(NO_ADAPT_WORKAROUND)
    struct identifier
    {
        std::string name;

        identifier() = default;

        explicit identifier(std::string name) 
            : name(std::move(name))
        {}
    };
#endif

struct problem
{
    identifier _1;
    identifier _2;
    identifier _3;
};

BOOST_FUSION_ADAPT_STRUCT(
    problem,
    (identifier, _1)
    (identifier, _2)
    (identifier, _3)
)

//////////////////////////////////////////
// For BOOST_SPIRIT_DEBUG only:
static inline std::ostream& operator<<(std::ostream& os, identifier const& id) {
    return os << id.name;
}
//////////////////////////////////////////

int main()
{
    using namespace qi;
    typedef std::string::const_iterator It;
#if defined(KEEP_STRING_WORKAROUND)
    rule<It, std::string()> gr_identifier = 
        (alpha | '_') >> *(alnum | '_');
#elif defined(DUMMY_WORKAROUND)
    rule<It, identifier()> gr_identifier = 
        (alpha | '_') >> *(alnum | '_') >> attr(42);   // that's hacky
#elif defined(NO_ADAPT_WORKAROUND)
    rule<It, identifier()> gr_identifier = 
        as_string [ (alpha | '_') >> *(alnum | '_') ]; // cleaner... but no fusion
#endif

    rule<It, problem(), qi::space_type> gr_problem = 
           gr_identifier
        >> gr_identifier
        >> ('(' > gr_identifier > ')')
        ;

    std::string input = "foo goo(hoo)";

    BOOST_SPIRIT_DEBUG_NODES((gr_problem)(gr_identifier));

    It f(begin(input)), l(end(input));
    bool dummy = phrase_parse(f, l, gr_problem, qi::space);

    return dummy? 0 : 255;
}

[1] Поверьте, я пытался, даже вставляя qi::unused_type "поддельные" атрибуты и / или использование attr_cast<> или вспомогательные правила для приведения типа подвыражения.

[2] Для демонстрации я использовал std::string потому что я считаю, что это лучше сочетается с BOOST_SPIRIT_DEBUG

Другие вопросы по тегам