Обнаружение типов параметров в семантическом действии Spirit

Общий случай: я не могу понять, почему мои грамматические/семантические действия Духа не компилируются.

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

  • прогнозирование типа синтезированных атрибутов для правила/выражения
    • следовательно, прогнозирование того, какие типы атрибутов могут быть юридически определены как открытый атрибут для правила (полагаясь на построение конверсий, адаптеров слияния или точек настройки Духа).
  • сопоставление типов аргументов для моего семантического действия, чтобы
    • компилятор сможет скомпилировать вызов функции
    • вызов не будет вызывать ненужные неявные преобразования в процессе

Ошибка компилятора не совсем выполнима, и либо документация неверна, либо я неправильно ее понял.

Есть ли способ узнать, что именно передает Дух в мое семантическое действие?

Пример кода:

struct mybase             { int a,b; };
struct myderived : mybase { int c,d; };

BOOST_FUSION_ADAPT_STRUCT(mybase,    (int,a)(int,b));
BOOST_FUSION_ADAPT_STRUCT(myderived, (int,a)(int,b)(int,c)(int,d));

auto base_expr = int_ >> int_; // avoids assigning to struct attribute

rule<decltype(f), mybase()   , space_type> base_       = int_ >> int_;
rule<decltype(f), myderived(), space_type> derived_    = base_ >> int_ >> int_;

myderived data;
bool ok = phrase_parse(f,l,derived_,space,data);

Этот код не будет компилироваться с огромным количеством непроницаемых ошибок.

(свободно адаптированный из публикации в дух-общий список)

Ответы

Ответ 1

Для ясности - ошибка здесь заключается в том, что base_ >> int_ >> int_ использовался как выражение для правила, которое создает myderived, и поскольку base_ фиксирован для типа mybase, нам нужно создать myderrived от a mybase и двух int s, но нечего сказать Духу, как это сделать.

Вы можете получить повышение, чтобы напечатать тип значения, которое создает форсирование при анализе base_ >> int_ >> int_, определяя функтор, который будет принимать какие-либо параметры, и расскажет вам, что они представляют (следующий код адаптирован из некоторого кода, поставленного в чате SO):

struct what_is_the_attr
{
    template <typename> struct result { typedef bool type; };

    template <typename T>
    static void print_the_type()
    {
        std::cout << "    ";
        std::cout << typeid(T).name();
        if(std::is_const<typename std::remove_reference<T>::type>::value)
            std::cout << " const";
        if(std::is_rvalue_reference<T>::value)
            std::cout << " &&";
        else if(std::is_lvalue_reference<T>::value)
            std::cout << " &";
    }

    template <typename Th, typename Th2, typename... Tt>
    static void print_the_type()
    {
        print_the_type<Th>();
        std::cout << ",\n";
        print_the_type<Th2, Tt...>();
    }

    template <typename... Ts>
    void operator()(Ts&&...) const
    {
        std::cout << "what_is_the_attr(\n";
        print_the_type<Ts...>();
        std::cout << ")" << std::endl;
    }
};

Затем, чтобы использовать его, используйте вышеупомянутый актер в семантическом действии на инициализаторе для вашего ошибочного правила:

std::string input = "1 2 3 4";
auto f(std::begin(input)), l(std::end(input));

rule<decltype(f), mybase()   , space_type> base_    = int_ >> int_;
rule<decltype(f), myderived(), space_type> derived_ = (base_ >> int_ >> int_)[what_is_the_attr()];

myderived data;
bool ok = phrase_parse(f,l,derived_,space,data);

Примечание. Вы не можете использовать автоматическое распространение атрибутов с помощью %= (если вы не удалите открытый тип атрибута из объявленного типа правила).

Запуск этого должен затем получить кодированный тип, который может быть декодирован с помощью c++filt -t: Live On Coliru

$ g++ 9404189.cpp -std=c++0x
$ ./a.out |c++filt -t
what_is_the_attr(
    boost::fusion::vector3<mybase, int, int> &,
    boost::spirit::context<boost::fusion::cons<boost::spirit::unused_type&, boost::fusion::nil>, boost::fusion::vector0<void> > &,
    bool &)

Первая строка, boost::fusion::vector3<mybase, int, int>, наименьшая говорит вам, что boost пытается создать ваш тип возврата из 3 объектов типов mybase, int и int.

Ответ 2

Я мог бы решить проблему для этого конкретного случая (фактически мы обсудили варианты в списке), но на самом деле этот вид "загадочная" ошибка ползет чаще с Boost Spirit, и было бы неплохо чтобы справиться с общим классом проблем.

Ваш первый ресурс должен быть отличной документацией по духу, которая детализирует то, что будет синтезированный атрибут для данного парсера примитив, оператор или директива. См. Ссылка раздел Дух Qi Docs.

В некоторых случаях я решил переключить фокус с "попытки поднять информация из списка ошибок компилятора 'to' активно запрашивает Spirit для типы, которые он передает ". Техника, которую я использую для этого, - это полиморфный вызываемый тип (см. Документы Spirit/Fusion).

Вот тот, который использует специальные API-интерфейсы GCC, чтобы довольно [sic] печатать типы, которые он обнаруживает:

Функтор what_is_the_attr

#include <cxxabi.h>
#include <stdlib.h>
#include <string>
#include <iostream>

template <typename T> std::string nameofType(const T& v) {
    int     status;
    char   *realname = abi::__cxa_demangle(typeid(v).name(), 0, 0, &status);
    std::string name(realname? realname : "????");
    free(realname);
    return name;
}

struct what_is_the_attr {
    template <typename> struct result { typedef bool type; };

    template <typename T> bool operator()(T& attr) const {
        std::cerr << "what_is_the_attr: " << nameofType(attr) << std::endl;
        return true;
    }
};

Использование примера: определение типа синтезируемого атрибута

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

template <typename Exp>
    void detect_attr_type(const Exp& exp)
{
    using namespace boost::spirit::qi;

    const char input[] = "1 2 3 4";
    auto f(std::begin(input)), l(std::end(input)-1);

    bool dummy = phrase_parse(
            f, l, 
            exp [ what_is_the_attr() ],
            space);
}

( Примечание: это показывает ограничение подхода - техника предполагает, что вы имеют "обратную" рабочую грамматику, и вы знаете, как передать ввод, который удовлетворяет выражению, достаточному для запуска семантического действия. В большинстве случаев, это будет верно, если вы будете взламывать ваш парсер Spirit, хотя)

Позвольте проверить его. Например. посмотрим, какая разница между и выражением среднюю сложность и ту же самую оболочку внутри директивы qi::raw[]:

int main()
{
    detect_attr_type(       -(int_ >> *int_)    );
    detect_attr_type( raw [ -(int_ >> *int_) ] );
}

Вывод:

what_is_the_attr: boost::optional<boost::fusion::vector2<int, std::vector<int, std::allocator<int> > > >
what_is_the_attr: boost::iterator_range<char const*>

Внизу мы применим это к вопросу в OP.

Пример использования: определение типов, переданных в Семантические действия

Мы могли бы использовать один и тот же унарный объект функции (what_is_the_attr) для обнаружения однако эти семантические действия могут принимать любое количество аргументов, поэтому нам нужно обобщать. Это было бы утомительной работой, если бы это было не для variadic шаблон (woot! для С++ 0x):

struct what_are_the_arguments {
    template <typename...> struct result { typedef bool type; };

    template <typename... T> bool operator()(const T&... attr) const {
        std::vector<std::string> names { nameofType(attr)... };
        std::cerr << "what_are_the_arguments:\n\t";
        std::copy(names.begin(), names.end(), std::ostream_iterator<std::string>(std::cerr, "\n\t"));
        std::cerr << '\n';
        return true;
    }
};

Повторение для вышеуказанных тестовых примеров показывает, что Дух на самом деле пытается вызвать семантическое действие с тремя аргументами, если это возможно (как документально):

what_are_the_arguments:
    boost::optional<boost::fusion::vector2<int, std::vector<int, std::allocator<int> > > >
    boost::spirit::unused_type
    bool

what_are_the_arguments:
    boost::iterator_range<char const*>
    boost::spirit::unused_type
    bool

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

template <typename ExpWSA> void test(const ExpWSA& exp)
{
    const char input[] = "1 2 3 4";
    auto f(std::begin(input)), l(std::end(input)-1);

    qi::phrase_parse(f, l, exp, qi::space);
}

int main()
{
    test(-(-double_ >> *int_) [ phx::bind(what_are_the_arguments(), _1, _2, _0, phx::ref(std::cout), 42) ]);
}

Печать, для этого (извините) очень надуманного примера:

what_are_the_arguments:
    boost::optional<double>
    std::vector<int, std::allocator<int> >
    boost::fusion::vector2<boost::optional<double>, std::vector<int, std::allocator<int> > >
    std::ostream
    int

Применяется к OP

Синтетический атрибут правила derived не совпадает с int_>>int_>>int_>>int_:

auto base_expr = int_ >> int_; // avoids assigning to struct attribute

rule<const char*, mybase(), space_type> base_       = base_expr;

test(base_     >> int_ >> int_ [ what_is_the_attr() ] );
test(base_expr >> int_ >> int_ [ what_is_the_attr() ] );

Будет напечатан

what_is_the_attr: boost::fusion::vector3<mybase, int, int>
what_is_the_attr: boost::fusion::vector4<int, int, int, int>

Есть твоя проблема. Мы обсудили некоторые обходные пути, основанные на этой диагностике, в исходном потоке (и см. Другие ответы здесь). Но этот пост должен помочь ответить на общий вопрос.

Полный список кодов

В интегрированной форме, скомпилированной с gcc 4.6.1 --std = С++ 0x и boost 1_48:

#include <cxxabi.h>
#include <iostream>
#include <iterator>
#include <stdlib.h>
#include <string>
#include <vector>

template <typename T> std::string nameofType(const T& v)
{
    int     status;
    char   *realname = abi::__cxa_demangle(typeid(v).name(), 0, 0, &status);
    std::string name(realname? realname : "????");
    free(realname);

    return name;
}

struct what_is_the_attr {
    template <typename> struct result { typedef bool type; };

    template <typename T> bool operator()(T& attr) const {
        std::cerr << "what_is_the_attr: " << nameofType(attr) << std::endl;
        return true;
    }
};

struct what_are_the_arguments {
    template <typename...> struct result { typedef bool type; };

    template <typename... T> bool operator()(const T&... attr) const {
        std::vector<std::string> names { nameofType(attr)... };
        std::cerr << "what_are_the_arguments:\n\t";
        std::copy(names.begin(), names.end(), std::ostream_iterator<std::string>(std::cerr, "\n\t"));
        std::cerr << '\n';
        return true;
    }
};

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>

struct mybase             { int a,b; };
struct myderived : mybase { int c,d; };

BOOST_FUSION_ADAPT_STRUCT(mybase,    (int,a)(int,b));
BOOST_FUSION_ADAPT_STRUCT(myderived, (int,a)(int,b)(int,c)(int,d));

template <typename ExpWSA>
void test(const ExpWSA& exp)
{
    using namespace boost::spirit::qi;

    const char input[] = "1 2 3 4";
    auto f(std::begin(input)), l(std::end(input)-1);

    bool dummy = phrase_parse(f, l, exp, space);
}

int main()
{
    using namespace boost::spirit::qi;

    // Diagnostics for the OP case
    auto base_expr = int_ >> int_;                                   // avoids assigning to struct attribute
    rule<const char*, mybase(), space_type> base_       = base_expr;

    // Derived rule, different formulations
    test((base_     >> int_ >> int_) [ what_is_the_attr() ] );
    test((base_expr >> int_ >> int_) [ what_is_the_attr() ] );

    // Applied to attribute types
    test(raw [ -(int_ >> *int_) ]  [ what_is_the_attr() ] );
    test(-(int_ >> *int_)          [ what_is_the_attr() ] );

    // applied to semantic actions - contrived example
    namespace phx = boost::phoenix;
    test(-(-double_ >> *int_) [ phx::bind(what_are_the_arguments(), _1, _2, _0, phx::ref(std::cout), 42) ]);

    return 0;
}