Search code examples
c++boostboost-spiritboost-spirit-x3

Automatic attribute propagation sometimes doesn't work when combining `%` and optional suffixes


Using boost 1.61, on both clang 3.8.0, and GCC 5.4, I get errors when compiling the following code:

#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
#include <tuple>
#include <vector>
#include <cassert>

void do_test(std::string input)
{
    namespace x3 = boost::spirit::x3;

    using x3::char_;
    using x3::lit;
    using x3::alpha;

    auto const word = +(alpha - ' ');


    auto first = begin(input);
    auto last = end(input);

    //this works
    {
        auto const& g = word;
        std::string out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = (word % ',');
        std::vector<std::string> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = lit('/') >> (word % ',');
        std::vector<std::string> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = -(word % ',');
        std::vector<std::string> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = -(lit('/') >> (word % ','));
        std::vector<std::string> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = -(lit('/') >> (word % ',') >> -lit('/'));
        std::vector<std::string> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = word >> lit(':') >> word;
        std::tuple<std::string, std::string> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = word >> lit(':') >> -(word % ',');
        std::tuple<std::string, std::vector<std::string>> out;
        x3::parse(first, last, g, out);
    }
    //but this fails.
    {
        auto const& g = word >> lit(':') >> -(lit('/') >> (word % ',') >> -lit('/'));
        std::tuple<std::string, std::vector<std::string>> out;
        x3::parse(first, last, g, out);
    }
    {
        auto const& g = word >> lit(':') >> -((word % ',') >> -lit('/'));
        std::tuple<std::string, std::vector<std::string>> out;
        x3::parse(first, last, g, out);
    }

}

with the following error message:

FAILED: /usr/bin/x86_64-linux-gnu-g++   -I../ext/boost-spirit/include -g -ggdb3   -fPIC -std=gnu++14 -MD -MT src/rtsp-header-parse/CMakeFiles/rtsp-header-parse.dir/rtsp-header-parse/test.cpp.o -MF src/rtsp-header-parse/CMakeFiles/rtsp-header-parse.dir/rtsp-header-parse/test.cpp.o.d -o src/rtsp-header-parse/CMakeFiles/rtsp-header-parse.dir/rtsp-header-parse/test.cpp.o -c ../src/rtsp-header-parse/rtsp-header-parse/test.cpp
In file included from ../ext/boost-spirit/include/boost/spirit/home/x3/auxiliary/any_parser.hpp:15:0,
                 from ../ext/boost-spirit/include/boost/spirit/home/x3/auxiliary.hpp:11,
                 from ../ext/boost-spirit/include/boost/spirit/home/x3.hpp:14,
                 from ../src/rtsp-header-parse/rtsp-header-parse/test.cpp:2:
../ext/boost-spirit/include/boost/spirit/home/x3/support/traits/container_traits.hpp: In instantiation of ‘struct boost::spirit::x3::traits::container_value<boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >, void>’:
../ext/boost-spirit/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:227:12:   required from ‘struct boost::spirit::x3::detail::parser_attr_is_substitute_for_container_value<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >, boost::spirit::x3::unused_type>’
../ext/boost-spirit/include/boost/mpl/aux_/nested_type_wknd.hpp:26:31:   required from ‘struct boost::mpl::aux::nested_type_wknd<boost::spirit::x3::detail::parser_attr_is_substitute_for_container_value<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >, boost::spirit::x3::unused_type> >’
../ext/boost-spirit/include/boost/mpl/not.hpp:39:8:   required from ‘struct boost::mpl::not_<boost::spirit::x3::detail::parser_attr_is_substitute_for_container_value<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >, boost::spirit::x3::unused_type> >’
../ext/boost-spirit/include/boost/mpl/aux_/nested_type_wknd.hpp:26:31:   required from ‘struct boost::mpl::aux::nested_type_wknd<boost::mpl::not_<boost::spirit::x3::detail::parser_attr_is_substitute_for_container_value<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >, boost::spirit::x3::unused_type> > >’
../ext/boost-spirit/include/boost/mpl/aux_/preprocessed/gcc/or.hpp:23:8:   required from ‘struct boost::mpl::aux::or_impl<false, boost::mpl::not_<boost::spirit::x3::detail::parser_attr_is_substitute_for_container_value<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >, boost::spirit::x3::unused_type> >, mpl_::bool_<false>, mpl_::bool_<false>, mpl_::bool_<false> >’
../ext/boost-spirit/include/boost/mpl/aux_/preprocessed/gcc/or.hpp:48:8:   [ skipping 7 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
../ext/boost-spirit/include/boost/spirit/home/x3/core/proxy.hpp:42:73:   required from ‘bool boost::spirit::x3::proxy<Subject, Derived>::parse(Iterator&, const Iterator&, const Context&, RuleContext&, Attribute&) const [with Iterator = __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> >; Context = boost::spirit::x3::unused_type; RuleContext = const boost::spirit::x3::unused_type; Attribute = boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >; Subject = boost::spirit::x3::sequence<boost::spirit::x3::list<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >; Derived = boost::spirit::x3::optional<boost::spirit::x3::sequence<boost::spirit::x3::list<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > > >]’
../ext/boost-spirit/include/boost/spirit/home/x3/operator/detail/sequence.hpp:312:13:   required from ‘bool boost::spirit::x3::detail::parse_sequence(const Parser&, Iterator&, const Iterator&, const Context&, RContext&, Attribute&, boost::spirit::x3::traits::tuple_attribute) [with Parser = boost::spirit::x3::sequence<boost::spirit::x3::sequence<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::sequence<boost::spirit::x3::list<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > > > >; Iterator = __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> >; Context = boost::spirit::x3::unused_type; RContext = const boost::spirit::x3::unused_type; Attribute = std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >]’
../ext/boost-spirit/include/boost/spirit/home/x3/operator/sequence.hpp:44:42:   required from ‘bool boost::spirit::x3::sequence<Left, Right>::parse(Iterator&, const Iterator&, const Context&, RContext&, Attribute&) const [with Iterator = __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> >; Context = boost::spirit::x3::unused_type; RContext = const boost::spirit::x3::unused_type; Attribute = std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >; Left = boost::spirit::x3::sequence<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >; Right = boost::spirit::x3::optional<boost::spirit::x3::sequence<boost::spirit::x3::list<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > > >]’
../ext/boost-spirit/include/boost/spirit/home/x3/core/parse.hpp:35:68:   required from ‘bool boost::spirit::x3::parse_main(Iterator&, Iterator, const Parser&, Attribute&) [with Iterator = __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> >; Parser = boost::spirit::x3::sequence<boost::spirit::x3::sequence<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::sequence<boost::spirit::x3::list<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > > > >; Attribute = std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >]’
../ext/boost-spirit/include/boost/spirit/home/x3/core/parse.hpp:47:26:   required from ‘bool boost::spirit::x3::parse(Iterator&, Iterator, const Parser&, Attribute&) [with Iterator = __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> >; Parser = boost::spirit::x3::sequence<boost::spirit::x3::sequence<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::sequence<boost::spirit::x3::list<boost::spirit::x3::plus<boost::spirit::x3::difference<boost::spirit::x3::char_class<boost::spirit::char_encoding::standard, boost::spirit::x3::alpha_tag>, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > >, boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> >, boost::spirit::x3::optional<boost::spirit::x3::literal_char<boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type> > > > >; Attribute = std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >]’
../src/rtsp-header-parse/rtsp-header-parse/test.cpp:72:38:   required from here
../ext/boost-spirit/include/boost/spirit/home/x3/support/traits/container_traits.hpp:76:12: error: no type named ‘value_type’ in ‘struct boost::fusion::iterator_range<boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 1>, boost::fusion::std_tuple_iterator<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, 2> >’
     struct container_value
            ^
In file included from ../ext/boost-spirit/include/boost/spirit/home/x3/directive/expect.hpp:12:0,
                 from ../ext/boost-spirit/include/boost/spirit/home/x3/auxiliary/guard.hpp:11,
                 from ../ext/boost-spirit/include/boost/spirit/home/x3/auxiliary.hpp:13,
                 from ../ext/boost-spirit/include/boost/spirit/home/x3.hpp:14,
                 from ../src/rtsp-header-parse/rtsp-header-parse/test.cpp:2:
...

I'm curious as to why the combination of the % operator with the inclusion of optional with an internal sequenced section causes a compilation to fail. ie: why does -(word % ',')work without issue, but -((word % ',') >> lit('/')) fail to parse into a subexpression? At the same time, why does it work when removing the prefix word and parsing directly into a std::vector<std::string>?

I've also tried to parse into a boost::mpl::vector<std::string, std::vector<std::string>> and get the same errors.


Solution

  • The short answer is: magic.

    The slightly longer answer is: single-element sequence attributes are sometimes considered as their single element instead.

    You are beating out the fine edge between those cases and finding that the "heuristic" choices made at various implementation points leak the "magic".

    Fixing Things

    I'm not sure what version you are on, and what inputs you are testing with. Actually, the source code suggests some problems:

    • alpha - ' ' is a pleonasm for alpha (' ' is not alpha)
    • this means you might have expected words to be separated by whitespace, which they're not unless you also have a skipper and put word under lexeme[] (see Boost spirit skipper issues)
    • You didn't check the results at all. Which means that the thing could possibly not parse anything, or partial input only. "Compiling" is not as interesting was "working"

    With that out of the way, here's my best guess at what you were trying to get at, with all examples working, so I hope that gives you some ideas:

    Demo

    #include <boost/fusion/adapted.hpp>
    #include <boost/spirit/home/x3.hpp>
    #include <tuple>
    #include <string>
    #include <vector>
    
    #include <iostream>
    #include <iomanip>
    
    namespace x3 = boost::spirit::x3;
    
    namespace {
        std::ostream& dump(std::ostream& os, std::string const& s) {
            return os << std::quoted(s);
        }
    
        template <typename T>
        std::ostream& dump(std::ostream& os, std::vector<T> const& v) {
            os << "vector{ ";
            for (auto& el : v)
                dump(os, el) << ", ";
            return os << "}";
        }
    
        template <typename A, typename B>
        std::ostream& dump(std::ostream& os, std::tuple<A, B> const& tuple) {
            dump(os << "\n  * tuple[0]: ", std::get<0>(tuple));
            dump(os << "\n  * tuple[1]: ", std::get<1>(tuple));
            return os;
        }
    }
    
    template <typename Out, typename It, typename Grammar>
    void do_test(It f, It l, Grammar const& g) {
        std::cout << " == input = " << std::quoted(std::string(f,l)) << "\n";
        Out out{};
        bool ok = x3::parse(f, l, g, out);
    
        std::cout << " -- ok = " << std::boolalpha << ok << "\n";
        dump(std::cout << " -- out = ", out) << "\n";
        if (f!=l)
            std::cout << " -- remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
    }
    
    namespace testing {
        #define TESTCASE(T, P)                                                                                  \
            do {                                                                                                \
                std::cout << "\n## " << __FUNCTION__ << " line: " << __LINE__ << " parser: " << std::quoted(std::string(#P)) << "\n";  \
                auto f = begin(input), l = end(input);                                                          \
                do_test<T>(f, l, P);                                                                            \
            } while (0)
    
        using std::string;
        using string_vec       = std::vector<string>;
        using string_string    = std::tuple<string, string>;
        using string_stringvec = std::tuple<string, string_vec>;
    
        static auto const word
            = x3::rule<struct word_, std::string> {"word"}
            = x3::graph >> *(x3::graph - ':' - ',' - '/')
            ;
    
        void value_only(std::string const& input) {
            TESTCASE(string, word);
            TESTCASE(string_vec, (word % ','));
            TESTCASE(string_vec, '/' >> (word % ','));
            TESTCASE(string_vec, -(word % ','));
            TESTCASE(string_vec, -('/' >> (word % ',')));
            TESTCASE(string_vec, -('/' >> (word % ',') >> -x3::lit('/')));
        }
    
        void key_value(std::string const& input) {
            TESTCASE(string_string,    word >> ':' >> word);
            TESTCASE(string_stringvec, word >> ':' >> -(word % ','));
            TESTCASE(string_stringvec, word >> ':' >> -('/' >> (word % ',') >> -x3::lit('/')));
            TESTCASE(string_stringvec, word >> ':' >> -((word % ',') >> -x3::lit('/')));
        }
    
        #undef TESTCASE
    }
    
    int main() {
        testing::value_only("/other,words/");
        testing::key_value("hello:/other,words/");
    }
    

    Boost 1.66 workaround?

    You can coerce the "value" part to be vector<string> using an additional rule. The simplest way to do this:

    Live On Wandbox w/boost 1.66

    void key_value(std::string const& input) {
        TESTCASE(string_string,    word >> ':' >> word);
        TESTCASE(string_stringvec, word >> ':' >> -(word % ','));
        auto asvec = [](auto p) {
            return x3::rule<struct asvec_, string_vec> {"asvec"} = p;
        };
        TESTCASE(string_stringvec, word >> ':' >> asvec(-('/' >> (word % ',') >> -x3::lit('/'))));
        TESTCASE(string_stringvec, word >> ':' >> asvec(-((word % ',') >> -x3::lit('/'))));
    }
    

    Output

    For completeness the programs print:

    ## value_only line: 64 parser: "word"
     == input = "/other,words/"
     -- ok = true
     -- out = "/other"
     -- remaining unparsed: ",words/"
    
    ## value_only line: 65 parser: "(word % ',')"
     == input = "/other,words/"
     -- ok = true
     -- out = vector{ "/other", "words", }
     -- remaining unparsed: "/"
    
    ## value_only line: 66 parser: "'/' >> (word % ',')"
     == input = "/other,words/"
     -- ok = true
     -- out = vector{ "other", "words", }
     -- remaining unparsed: "/"
    
    ## value_only line: 67 parser: "-(word % ',')"
     == input = "/other,words/"
     -- ok = true
     -- out = vector{ "/other", "words", }
     -- remaining unparsed: "/"
    
    ## value_only line: 68 parser: "-('/' >> (word % ','))"
     == input = "/other,words/"
     -- ok = true
     -- out = vector{ "other", "words", }
     -- remaining unparsed: "/"
    
    ## value_only line: 69 parser: "-('/' >> (word % ',') >> -x3::lit('/'))"
     == input = "/other,words/"
     -- ok = true
     -- out = vector{ "other", "words", }
    
    ## key_value line: 73 parser: "word >> ':' >> word"
     == input = "hello:/other,words/"
     -- ok = true
     -- out = 
      * tuple[0]: "hello"
      * tuple[1]: "/other"
     -- remaining unparsed: ",words/"
    
    ## key_value line: 74 parser: "word >> ':' >> -(word % ',')"
     == input = "hello:/other,words/"
     -- ok = true
     -- out = 
      * tuple[0]: "hello"
      * tuple[1]: vector{ "/other", "words", }
     -- remaining unparsed: "/"
    
    ## key_value line: 78 parser: "word >> ':' >> asvec(-('/' >> (word % ',') >> -x3::lit('/')))"
     == input = "hello:/other,words/"
     -- ok = true
     -- out = 
      * tuple[0]: "hello"
      * tuple[1]: vector{ "other", "words", }
    
    ## key_value line: 79 parser: "word >> ':' >> asvec(-((word % ',') >> -x3::lit('/')))"
     == input = "hello:/other,words/"
     -- ok = true
     -- out = 
      * tuple[0]: "hello"
      * tuple[1]: vector{ "/other", "words", }