diff --git a/include/boost/parser/parser.hpp b/include/boost/parser/parser.hpp index dbf0596b..a51255c3 100644 --- a/include/boost/parser/parser.hpp +++ b/include/boost/parser/parser.hpp @@ -1735,9 +1735,17 @@ namespace boost { namespace parser { using just_t = remove_cv_ref_t; using just_u = remove_cv_ref_t; if constexpr ( + !is_tuple::value && is_tuple::value && + std::is_aggregate_v && + !std::is_convertible_v && + is_struct_assignable_v) { + auto int_seq = + std::make_integer_sequence>(); + t = detail::tuple_to_aggregate(std::move(u), int_seq); + } else if constexpr ( is_tuple::value && !is_tuple::value && std::is_aggregate_v && - !std::is_convertible_v && + !std::is_convertible_v && is_tuple_assignable_v) { auto tie = detail::tie_aggregate(u); detail::aggregate_to_tuple( @@ -2360,6 +2368,61 @@ namespace boost { namespace parser { retval = false; return retval; } + + // The notion of comaptibility is that, given a parser with the + // Attribute Tuple, we can parse into Struct instead. + template + constexpr auto is_struct_compatible(); + + struct element_compatibility + { + template + constexpr auto operator()(T result, U x) const + { + using struct_elem = + remove_cv_ref_t{}))>; + using tuple_elem = + remove_cv_ref_t{}))>; + if constexpr (!T::value) { + return std::false_type{}; + } else if constexpr (std::is_convertible_v< + tuple_elem &&, + struct_elem>) { + return std::true_type{}; + } else if constexpr ( + container && container) { + return detail::is_struct_compatible< + range_value_t, + range_value_t>(); + } else { + return std::bool_constant()>{}; + } + } + }; + + template + constexpr auto is_struct_compatible() + { + if constexpr ( + !std::is_aggregate_v || + struct_arity_v != tuple_size_) { + return std::false_type{}; + } else { + using result_t = decltype(detail::hl::fold_left( + detail::hl::zip( + detail::tie_aggregate(std::declval()), + std::declval()), + std::true_type{}, + element_compatibility{})); + return result_t{}; + } + } + + template + constexpr bool is_struct_compatible_v = + detail::is_struct_compatible(); } @@ -3277,9 +3340,7 @@ namespace boost { namespace parser { detail::assign(retval, std::move(attr)); } else if constexpr ( detail::is_tuple{} || - (std::is_aggregate_v && - detail:: - is_struct_assignable_v)) { + detail::is_struct_compatible_v) { call_impl( use_cbs, first, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 16e04140..2ee738a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,7 +6,6 @@ enable_testing() add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -j4 -C ${CMAKE_CFG_INTDIR}) - ################################################## # Parser tests ################################################## diff --git a/test/parser.cpp b/test/parser.cpp index d6d0bcee..d933fb3b 100644 --- a/test/parser.cpp +++ b/test/parser.cpp @@ -1843,3 +1843,37 @@ TEST(parser, github_issue_36) // Intentionally ill-formed. // bp::parse("i1 i2", ids, bp::ascii::space, str); // (3) } + +namespace issue_50 { + struct X + { + char a; + int b; + }; + + struct Y + { + std::vector x; + int c; + }; +} + +TEST(parser, github_issue_50) +{ + using namespace issue_50; + + namespace bp = boost::parser; + + { + auto parse_x = bp::char_ >> bp::int_; + auto parse_y = +parse_x >> bp::int_; + + Y y; + auto b = bp::parse("d 3 4", parse_y, bp::ws, y); + EXPECT_TRUE(b); + + EXPECT_EQ(y.x[0].a, 'd'); + EXPECT_EQ(y.x[0].b, 3); + EXPECT_EQ(y.c, 4); + } +}