Search code examples
c++boost-spirit

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.


Solution

  • 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