简体   繁体   中英

Boost Spirit Qi storing into std::vector using repeat leads to ambiguous class template instantiation

Storing the result of a repeat statement into a std::vector leads to compile error:

/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:172:12: error: ambiguous 

    class template instantiation for ‘struct boost::spirit::qi::detail::pass_through_container_base<std::vector<Vertex3d<float> >, Vertex3d<float>, Vertex3d<float>, mpl_::bool_<false>, void>’

/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:103:12: error: candidates are: struct boost::spirit::qi::detail::pass_through_container_base<Container, ValueType, Attribute, Sequence, typename boost::enable_if<boost::fusion::traits::is_sequence<Attribute> >::type>
     struct pass_through_container_base<Container, ValueType, Attribute
            ^
/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:136:12: error:                 struct boost::spirit::qi::detail::pass_through_container_base<Container, ValueType, Attribute, Sequence, typename boost::enable_if<boost::spirit::traits::is_container<T2> >::type>
     struct pass_through_container_base<
            ^
/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:172:12: error: invalid use of incomplete type ‘struct boost::spirit::qi::detail::pass_through_container_base<std::vector<wc3lib::Vertex3d<float> >, wc3lib::Vertex3d<float>, wc3lib::Vertex3d<float>, mpl_::bool_<false>, void>’
     struct pass_through_container
            ^
/usr/include/boost/spirit/home/qi/detail/pass_container.hpp:50:12: error: declaration of ‘struct boost::spirit::qi::detail::pass_through_container_base<std::vector<wc3lib::Vertex3d<float> >, wc3lib::Vertex3d<float>, wc3lib::Vertex3d<float>, mpl_::bool_<false>, void>’
     struct pass_through_container_base

The following code is used for the grammar:

qi::rule<Iterator, long32(), Skipper> integer_literal;
qi::rule<Iterator, float32(), Skipper> real_literal;
qi::rule<Iterator, VertexReal3d(), Skipper> vertex_real_3d;
qi::rule<Iterator, Geoset::Vertices(), Skipper, qi::locals<long32> > vertices;


integer_literal %=
        lexeme[
            qi::int_parser<long32>()
        ]
    ;

real_literal %=
        lexeme[
            qi::real_parser<float32>()
        ]
    ;

vertex_real_3d =
        lit('{')
        >> real_literal[at_c<0>(_val) = _1]
        >> lit(',')
        >> real_literal[at_c<1>(_val) = _1]
        >> lit(',')
        >> real_literal[at_c<2>(_val) = _1]
        >> lit('}')
    ;

vertices =
        lit("Vertices")
        >> integer_literal[_a = _1]
        >> lit('{')
        >> repeat(_a)[
            vertex_real_3d
            >> lit(',')
        ][_val = _1] // Does not work?
        >> lit('}')
    ;

...

typedef Vertex3d<float32> VertexData;
typedef VertexData VertexReal3d;
typedef std::vector<VertexData> Vertices;

...

BOOST_FUSION_ADAPT_ADT(
    wc3lib::mdlx::VertexData,
    (wc3lib::float32, wc3lib::float32, obj[0], obj[0] = val)
    (wc3lib::float32, wc3lib::float32, obj[1], obj[1] = val)
    (wc3lib::float32, wc3lib::float32, obj[2], obj[2] = val)
)

...

template<typename T, typename std::size_t N>
class BasicVertex : public std::array<T, N>
{
    public:
        typedef std::array<T, N> Base;

        BasicVertex() : Base()
        {
        }

        BasicVertex(const BasicVertex<T, N> &other) : Base(other) {
    }


};


template<typename T = float32>
class Vertex3d : public BasicVertex<T, 3>
{
    public:
        typedef BasicVertex<T, 3> Base;

        Vertex3d() : Base()
        {
        }

        Vertex3d(T x, T y, T z)
        {
            (*this)[0] = x;
            (*this)[1] = y;
            (*this)[2] = z;
        }

        Vertex3d(const Base &other) : Base(other) {
        }
};

The rule vertices should return a std::vector of VertexData. Therefore repeat is used to parse a fixed amount of vertices. The amount is placed as an integer value in the parsed file before the list and stored in _a.

The compile error hints that it cannot differ between "is_sequence" and "is_container". I am not an expert of Spirit, so I cannot answer what it does mean exactly.

Here's the result of my making it self contained.

Live On Coliru

I chose to adapt the VectorData type. (I noticed too late you probably didn't).

This makes the grammar over complicated. I'm not sure exactly what didn't work because well, by the time the code compiled it did work...

So let's make this another exercise in code cleanup and lead-by-example:

  • removed semantic actions
  • removed two rules
  • added debug information
  • removed the quirky required trailing ',' here:

     '{' >> repeat(_a)[ vertex_real_3d >> (',' | &lit('}')) ] >> '}' 
  • removed the redundant uses of operator%=

  • removed the redundant uses of qi::lit()
  • don't expose the qi::locals<> in the grammar declaration (it's an implementation detail)

Now the code clocks in at 77 lines (25 lines fewer than before):

Live On Coliru

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

namespace qi = boost::spirit::qi;

using long32  = int32_t;
using float32 = float;

namespace Geoset {
    template <typename T>
        struct Vertex3d {
            T a,b,c;
        };

    typedef Vertex3d<float32>       VertexData;
    typedef VertexData              VertexReal3d;
    typedef std::vector<VertexData> Vertices;
}

BOOST_FUSION_ADAPT_STRUCT(Geoset::VertexData, (float32, a)(float32, b)(float32, c))

template <typename Iterator, typename Skipper = qi::space_type>
struct grammar : qi::grammar<Iterator, Geoset::Vertices(), Skipper> {

    grammar() : grammar::base_type(start) {
        using namespace qi;

        vertex_real_3d = 
            '{' >> real_literal >> ','
                >> real_literal >> ','
                >> real_literal >> '}'
            ;

        vertices %=
                "Vertices"
                >> omit [ integer_literal[_a = _1] ]
                >> '{' >> repeat(_a)[ vertex_real_3d >> (',' | &lit('}')) ] >> '}'
            ;

        start = vertices;

        BOOST_SPIRIT_DEBUG_NODES((start)(vertices)(vertex_real_3d))
    }
  private:
    qi::int_parser<long32> integer_literal;
    qi::real_parser<float32> real_literal;
    qi::rule<Iterator, Geoset::VertexReal3d(), Skipper> vertex_real_3d;
    qi::rule<Iterator, Geoset::Vertices(), Skipper, qi::locals<long32> > vertices;
    qi::rule<Iterator, Geoset::Vertices(), Skipper> start;
};

int main() {
    std::string const input = "Vertices 4 { \n"
        " { 1,  2,  3  }, \n"
        " { 4,  5,  6  }, \n"
        " { 7,  8,  9  }, \n"
        " { 10, 11, 12 } \n"
    "}";

    auto f(begin(input)), l(end(input));
    grammar<std::string::const_iterator> g;

    Geoset::Vertices vertices;
    bool ok = qi::phrase_parse(f,l,g,qi::space,vertices);

    if (ok) {
        std::cout << "Parsed: " << vertices.size() << "\n";
        for (auto& v : vertices)
            std::cout << boost::fusion::as_vector(v) << "\n";
    } else
        std::cout << "Parse failed\n";

    if (f!=l)
        std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
}

Ok, with the edits, the question became viable.

Indeed with a (public) base class of std::array<> and Fusion adaptation at the same time (luckily¹!) Spirit cannot decide which of the attribute assignment paths is to be taken.

So fix it either by

  • telling Spirit "These are not the droids you're looking for" (when it thinks for a moment that it's seeing a container):

     namespace boost { namespace spirit { namespace traits { template <> struct is_container<wc3lib::mdlx::VertexData> : mpl::false_ { }; } } } 

    Live On Coliru

  • not (publicly) deriving from std::array

I think you may want to ask yourself what the point is of inheriting from std::array<> in the first place. And the way your Vertex types are defined now explicitly makes them non-POD, which means that performance is going to suffer.

Consider just

template <typename T, typename std::size_t N> class BasicVertex : public std::array<T,N> {
public:
    typedef std::array<T, N> Base;
};

template <typename T = float> class Vertex3d : public BasicVertex<T, 3> {
public:
    typedef BasicVertex<T, 3> Base;
};

or

template <typename T, typename std::size_t N> class BasicVertex {
public:
    std::array<T, N> data_;
};

template <typename T = float> class Vertex3d : public BasicVertex<T, 3> {
};

for some performance. In reality, I'd probably write

template <typename T = float> struct Vertex3d {
    T x, y, z;
};

¹ believe me, you want a library to diagnose this kind of situation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM