c++stringboost-spiritstring-view

Compile error with boost::spirit::x3 v1.84


Note that this code compiles without error with spirit x3 from boost v1.69, for which this code was originally developed. The error originates at the x3::parse() line, but with this template noise I have no idea what the compiler is actually trying to tell me or why spirit x3 1.84 has this error while 1.69 doesn't. g++ 13.2 in c++17 mode:

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

#include <algorithm>
#include <iomanip>
#include <iterator>
#include <stdexcept>

namespace x3 = boost::spirit::x3;

namespace {

using x3::char_;
using x3::lit;
using x3::no_skip;

const auto escaped =
    lit('"') >> *((char_ - '"') | lit('"') >> char_('"')) >> lit('"');
const auto non_escaped =
    *(char_ - (lit(',') | lit('"') | lit('\r') | lit('\n')));
const auto field = escaped | non_escaped;
const auto record = (field % ',') >> (lit("\r\n") | lit('\n') | !char_);

} //namespace

namespace utils {

std::vector<std::string> parse_csv(std::string_view input,
    std::string_view& remainder)
{
    std::vector<std::string> result;
    auto first { std::begin(input) };
    if (!x3::parse(first, std::end(input), record, result)) {
        throw std::runtime_error{"CSV parse failure"};
    }
    remainder = input.substr(std::distance(std::begin(input), first));
    return result;
}

}

After paring down what I hope is many lines of error messages not salient to the point:

/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:164:9: note:   template argument deduction/substitution failed:
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:196:24: note:   deduced conflicting types for parameter ‘Iterator’ (‘char’ and ‘std::__cxx11::basic_string<char>’)
  196 |         detail::move_to(src, dest, typename attribute_category<Dest>::type());
      |         ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:168:9: note: candidate: ‘template<class Iterator, class Dest> void boost::spirit::x3::traits::detail::move_to(Iterator, Iterator, Dest&, boost::spirit::x3::traits::container_attribute)’
  168 |         move_to(Iterator first, Iterator last, Dest& dest, container_attribute)
      |         ^~~~~~~
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:168:9: note:   template argument deduction/substitution failed:
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:196:24: note:   deduced conflicting types for parameter ‘Iterator’ (‘char’ and ‘std::__cxx11::basic_string<char>’)
  196 |         detail::move_to(src, dest, typename attribute_category<Dest>::type());
      |         ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:180:9: note: candidate: ‘template<class Iterator, class Dest> typename boost::enable_if<boost::spirit::x3::traits::is_size_one_sequence<Dest> >::type boost::spirit::x3::traits::detail::move_to(Iterator, Iterator, Dest&, boost::spirit::x3::traits::tuple_attribute)’
  180 |         move_to(Iterator first, Iterator last, Dest& dest, tuple_attribute)
      |         ^~~~~~~
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:180:9: note:   template argument deduction/substitution failed:
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:196:24: note:   deduced conflicting types for parameter ‘Iterator’ (‘char’ and ‘std::__cxx11::basic_string<char>’)
  196 |         detail::move_to(src, dest, typename attribute_category<Dest>::type());
      |         ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:187:9: note: candidate: ‘template<class Iterator> void boost::spirit::x3::traits::detail::move_to(Iterator, Iterator, boost::iterator_range<T>&, boost::spirit::x3::traits::range_attribute)’
  187 |         move_to(Iterator first, Iterator last, boost::iterator_range<Iterator>& rng, range_attribute)
      |         ^~~~~~~
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:187:9: note:   template argument deduction/substitution failed:
/data3/jbuster/boost-build/install/xgcc/boost/1.84/include/boost/spirit/home/x3/support/traits/move_to.hpp:196:24: note:   deduced conflicting types for parameter ‘Iterator’ (‘char’ and ‘std::__cxx11::basic_string<char>’)
  196 |         detail::move_to(src, dest, typename attribute_category<Dest>::type());
      |         ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

https://godbolt.org/z/ParTY34zf


Solution

  • The attribute synthesis/compatibility rules for X3 have been in flux. In this particular area (string synthesis) I've seen some improvements but also "random behaviour changes".

    This is all fine since the X3 interface is (still) documented as "experimental".

    Help the attribute synthesis a little, changing

    auto const field = escaped | non_escaped;
    

    to

    auto const field = x3::rule<void, std::string> {} = escaped | non_escaped;
    

    Here's a demo with test cases and many many simplifications/improvements along the way:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    namespace CsvParser {
        namespace x3           = boost::spirit::x3;
        auto const escaped     = '"' >> *(~x3::char_('"') | '"' >> x3::char_('"')) >> '"';
        auto const non_escaped = *~x3::char_(",\"\r\n");
        auto const field       = x3::rule<void, std::string> {} = escaped | non_escaped;
        auto const record      = (field % ',') >> (x3::eol | x3::eoi);
    } // namespace
    
    namespace utils {
        std::vector<std::string> parse_csv_record(std::string_view input, std::string_view& remainder) {
            std::vector<std::string> result;
    
            if (auto f{std::begin(input)}; parse(f, std::end(input), CsvParser::record, result))
                remainder = input.substr(std::distance(std::begin(input), f));
            else
                throw std::runtime_error{"CSV parse failure"};
    
            return result;
        }
    } // namespace utils
    
    #include <fmt/ranges.h>
    int main() {
        std::string_view demo = R"(
    foo,bar,qux
    1,"some, not all, things","are",created equal,
    
    ,,
    ,"
    'multi'
    ""line"" baby"
    )";
    
        for (int lno = 1; !demo.empty(); ++lno) {
            auto v = utils::parse_csv_record(demo, demo);
            fmt::print("line #{}: {}\n", lno, v);
        }
    }
    

    Printing the expectable

    line #1: [""]
    line #2: ["foo", "bar", "qux"]
    line #3: ["1", "some, not all, things", "are", "created equal", ""]
    line #4: [""]
    line #5: ["", "", ""]
    line #6: ["", "\n'multi'\n\"line\" baby"]
    

    BONUS

    Parse a whole file at once:

    Live On Coliru

    namespace Csv {
        using Record = std::vector<std::string>;
        using File   = std::vector<Record>;
        File parse(std::string_view input) {
            File result;
            parse(std::begin(input), std::end(input), CsvParser::record % x3::eol > x3::eoi, result);
            return result;
        }
    } // namespace Csv
    

    Printing

    parsed
    -----
    ["foo", "bar", "qux"]
    ["1", "some, not all, things", "are", "created equal", ""]
    [""]
    ["", "", ""]
    ["", "\n'multi'\n\"line\" baby"]
    

    ¹ e.g. the case where raw[p] >> raw[p] suddenly becomes compatible with a std::string bound attribute reference; I'm not sure I concur