Проблема распространения атрибута 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