Могу ли я прочитать файл и построить разнородные объекты во время компиляции?

Ситуация:

Файл YAML, содержащий список разнородных объектов по имени, например:

object: Foo
  name: Joe Bloggs
  age: 26
object: Bar
  location: UK

Объекты не наследуют какой-либо базовый класс и не имеют каких-либо отношений друг с другом, за исключением того факта, что они «живут» вместе.

Он может содержать любое количество объектов. Список доступных типов может существовать в списке типов в кодовой базе, если это необходимо.

В моей стране С++ у меня есть объекты:

struct Foo {
  Foo(std::string n, int a) : name(n), age(a) {}

  std::string name;
  int age;
};

struct Bar {
  Bar(std::string l) : location(l) {}

  std::string location;
};

И при компиляции я хочу превратить этот файл YAML в boost::fusion::vector:

boost::fusion::vector<Foo, Bar>(Foo("Joe Bloggs", 26), Bar("UK"));

Or:

boost::fusion::vector<Foo, Bar>(make_obj<Foo>("Joe Bloggs", 26), make_obj<Bar>("UK"));

Также может быть std::tuple, если это облегчает жизнь.

При необходимости специализации для make_obj могут существовать для всех поддерживаемых объектов.

Это возможно?

Готов ли я запачкать руки MPL / другим продвинутым метапрограммированием, если это необходимо, или могу ли я сделать все это с помощью constexpr?

Версия C++ не беспокоит, при необходимости можно использовать магистральный Clang C++14.


person Sam Kellett    schedule 18.12.2014    source источник


Ответы (1)


Я вижу два основных подхода:

С временем компиляции «Отражение»

Вы можете использовать BOOST_FUSION_ADAPT_STRUCT, получить свой пирог и съесть его. Если вы адаптируете свои структуры, вы можете статически повторять их - фактически писать этот генератор кода, о котором упоминал @πάνταῥεῖ, но встроенный в код C++ и во время компиляции.

Вы можете статически ограничить типы с помощью варианта.

С ручной грамматикой

Используя Boost Spirit, вы можете просто создать грамматику для тех же структур:

    start   = *(label_(+"object") >> object_);
    object_ = foo_ | bar_;

    foo_    = "Foo" >> eol >> (
                (string_prop_(+"name") >> eol) ^
                (int_prop_(+"age") >> eol)
            );

    bar_    = "Bar" >> eol >> (
                (string_prop_(+"location") >> eol)
            );

    label_  = lit(_r1) >> ':';

    string_prop_ = label_(_r1) >> lexeme [ *(char_ - eol) ];
    int_prop_    = label_(_r1) >> int_;

Теперь это анализируется в variant<Foo, Bar> без дальнейшего кодирования. Он даже позволяет name и age появляться в случайном порядке (или принимать значение по умолчанию). Конечно, если вам не нужна такая гибкость, замените ^ на >> в грамматике.

Вот пример ввода:

object: Foo
  name: Joe Bloggs
  age: 26
object: Foo
  age: 42
  name: Douglas Adams
object: Foo
  name: Lego Man
object: Bar
  location: UK

А вот хвост образца (отладки):

<success></success>
<attributes>[[[[J, o, e,  , B, l, o, g, g, s], 26], [[D, o, u, g, l, a, s,  , A, d, a, m, s], 42], [[L, e, g, o,  , M, a, n], 0], [[U, K]]]]</attributes>
</start>
Parse success: 4 objects
N4data3FooE (Joe Bloggs 26)
N4data3FooE (Douglas Adams 42)
N4data3FooE (Lego Man 0)
N4data3BarE (UK)

Жить на Coliru

#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/bind.hpp>
#include <fstream>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

namespace demo {
    struct visitor : boost::static_visitor<> {
        template<typename Seq>
            void operator()(std::ostream& os, Seq const& seq) const {
                os << typeid(Seq).name() << "\t" << boost::fusion::as_vector(seq);
            }
    };
}

namespace data {
    struct Foo {
        Foo(std::string n="", int a=0) : name(n), age(a) {}

        std::string name;
        int age;
    };

    struct Bar {
        Bar(std::string l="") : location(l) {}

        std::string location;
    };

    using object  = boost::variant<Foo, Bar>;
    using objects = std::vector<object>;

    std::ostream& operator<< (std::ostream& os, object const& o) {
        boost::apply_visitor(boost::bind(demo::visitor(), boost::ref(os), _1), o);
        return os;
    }
}

BOOST_FUSION_ADAPT_STRUCT(data::Foo,(std::string,name)(int,age))
BOOST_FUSION_ADAPT_STRUCT(data::Bar,(std::string,location))

template <typename It>
struct grammar : qi::grammar<It, data::objects(), qi::blank_type> {
    grammar() : grammar::base_type(start) {
        using namespace qi;

        start   = *(label_(+"object") >> object_);
        object_ = foo_ | bar_;

        foo_    = "Foo" >> eol >> (
                    (string_prop_(+"name") >> eol) ^
                    (int_prop_(+"age") >> eol)
                );

        bar_    = "Bar" >> eol >> (
                    (string_prop_(+"location") >> eol)
                );

        label_  = lit(_r1) >> ':';

        string_prop_ = label_(_r1) >> lexeme [ *(char_ - eol) ];
        int_prop_    = label_(_r1) >> int_;

        BOOST_SPIRIT_DEBUG_NODES((start)(object_)(foo_)(bar_)(label_)(string_prop_)(int_prop_));
    }
  private:
    qi::rule<It, data::objects(), qi::blank_type> start;
    qi::rule<It, data::object(),  qi::blank_type> object_;
    qi::rule<It, data::Foo(),     qi::blank_type> foo_;
    qi::rule<It, data::Bar(),     qi::blank_type> bar_;

    qi::rule<It, std::string(std::string), qi::blank_type> string_prop_;
    qi::rule<It, int(std::string), qi::blank_type>         int_prop_;
    qi::rule<It, void(std::string), qi::blank_type>        label_;
};

int main()
{
    using It = boost::spirit::istream_iterator;
    std::ifstream ifs("input.txt");
    It f(ifs >> std::noskipws), l;

    grammar<It> p;
    data::objects parsed;
    bool ok = qi::phrase_parse(f,l,p,qi::blank,parsed);
    if (ok)
    {
        std::cout << "Parse success: " << parsed.size() << " objects\n";
        for(auto& object : parsed)
            std::cout << object << "\n";
    } else
    {
        std::cout << "Parse failed\n";
    }

    if (f!=l)
        std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
person sehe    schedule 18.12.2014
comment
Это тоже была моя первая мысль, и мне она нравится, но я сомневаюсь, потому что игнорирование пробелов кажется очень несовместимым с YAML. - person Mooing Duck; 19.12.2014
comment
@MooingDuck Я понятия не имею, что такое YAML. Конечно, это в значительной степени не имеет значения, потому что легко сделать так, чтобы грамматика не пропускала пробелы. Я показывал концепцию, а не грамматику (я думаю, это для ОП) - person sehe; 19.12.2014
comment
Его входными данными является YetAnotherMarkupLanguage, который использует отступ, чтобы указать, какие записи являются дочерними для каждого. На самом деле, это, вероятно, очень хорошо отражает его конкретный вклад. Это просто неправильно обработает искаженные входные данные. - person Mooing Duck; 19.12.2014
comment
Я нахожу эту ссылку слегка интересной. Я почти уверен, что он не спрашивал о том, как анализировать YAML. Поэтому я оставлю это в качестве упражнения читателю. - person sehe; 19.12.2014
comment
Да, это достаточно справедливо - person Mooing Duck; 19.12.2014
comment
Я только что добавил демонстрацию того, как использовать этот вариант (это может быть неочевидно). Кроме того, пусть запись показывает, что пробелы игнорируются, но это не включает в себя концы строк или пробелы внутри строковых свойств. :) - person sehe; 19.12.2014