Roby Roby - 3 months ago 17
C++ Question

repeat to std::tuple with known N at compile time

i want to parse a at compile time specified number of elements. I've tried the

repeat()[]
directive. The following code shows my case:

using namespace x3;
std::tuple<float, float, float> tup;
std::string str{"0.3 0.2 0.1"};
auto ret = parse(std::begin(str), std::end(str), repeat(3)[ float_ >> (' ' | eol) ] , tup);


The compiler error message:

error: static assertion failed: Expecting a single element fusion sequence
static_assert(traits::has_size<Attribute, 1>::value


It works if i would write it out:

parse(std::begin(str), std::end(str), float_ >> ' ' >> float_ >> ' ' >> float_ ] , tup);


but with a large number of elements it is confusion.

Is there a way to shorten the grammar with a 'repeat' directive ?

jv_ jv_
Answer

As you can see here the synthesized attribute of x3::repeat(3)[x3::float_] is a vector<float> and that does not match your attribute (basically a fusion sequence of size 3). Note that the synthesized attribute does not depend on the value you pass.

In order to get what you want you'll need another directive, one which type does depend on the value you pass. This directive would then generate a sequence where it's subject is repeated N times (simply "delegating" the work to x3::sequence would make sure that everything works correctly in regards to attribute propagation). I can think of at least two ways this could work: something like repeat<N>[parser] or something like repeat(integral_constant<int,N>)[parser]. In the code below I have chosen the second approach using boost::hana::integral_constant, which allows you to use:

custom::repeat(3_c)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ]

Full Code (Running on WandBox)

custom_repeat.hpp

#include <type_traits>
#include <boost/spirit/home/x3.hpp> 

namespace custom
{
    struct repeat_gen
    {
        template <int Size>
        struct repeat_gen_lvl1
        {
            template <typename Parser, int N>
            struct build_sequence //this builds the type of a sequence that has `Parser` as its right argument and a sequence on N-1 elements as its left one
            {
                using type = boost::spirit::x3::sequence<typename build_sequence<Parser,N-1>::type, Parser>;
            };

            template <typename Parser>
            struct build_sequence<Parser, 1> //a sequence of one is just `Parser`
            {
                using type = Parser;
            };

            //using overloads with integral constants to avoid needing to partially specialize a function

            //this actually builds the sequence of parsers
            template <typename Parser,int N>
            typename build_sequence<Parser,N>::type generate_sequence(Parser const& parser, std::integral_constant<int,N>) const
            {
                return {generate_sequence(parser,std::integral_constant<int,N-1>{}), parser};
            }

            template <typename Parser>
            typename build_sequence<Parser,1>::type generate_sequence(Parser const parser,std::integral_constant<int,1>) const
            {
                return parser;
            }

            template<typename Subject>
            typename build_sequence< typename boost::spirit::x3::extension::as_parser<Subject>::value_type, Size>::type
            operator[](Subject const& subject) const
            {
                //here the actual sequence is generated
                return generate_sequence(boost::spirit::x3::as_parser(subject), std::integral_constant<int,Size>{});
            }
        };

        template <typename IntConstant>
        repeat_gen_lvl1<int(IntConstant::value)>
        operator()(IntConstant) const
        {
            //returns an object that know the size of the wanted sequence and has an operator[] that will capture the subject
            return {};
        }
    };

    //this object's only purpose is having an operator()
    auto const repeat = repeat_gen{};
}

main.cpp

#include <iostream>

#include <boost/spirit/home/x3.hpp>

#include <boost/fusion/include/std_tuple.hpp>

#include <boost/hana/integral_constant.hpp>

#include <boost/mpl/int.hpp>

#include "custom_repeat.hpp"

namespace x3 = boost::spirit::x3;

using namespace boost::hana::literals;

template <typename Parser>
void parse(const Parser& parser)
{
    std::tuple<float, float, float> tup;
    const std::string str{"0.3 0.2 0.1"};
    std::string::const_iterator iter = std::begin(str), end = std::end(str);
    bool ret = x3::parse(iter, end, parser, tup);

    if(ret && (iter==end))
    {
        std::cout << "Success.\n";
        std::cout << "[" << std::get<0>(tup) << "][" << std::get<1>(tup) << "][" << std::get<2>(tup) << "]" << std::endl;
    }
    else
    {
        std::cout << "Something failed. Unparsed: ->|" << std::string(iter,end) << "|<-" << std::endl;
    }
}

int main()
{
    parse(custom::repeat(3_c)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );
    parse(custom::repeat(boost::mpl::int_<3>())[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );
    parse(custom::repeat(std::integral_constant<int,3>{})[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );

}
Comments