diff --git a/include/boost/spirit/home/x3/directive/with.hpp b/include/boost/spirit/home/x3/directive/with.hpp index 8cd9eb6fe..3f9eda514 100644 --- a/include/boost/spirit/home/x3/directive/with.hpp +++ b/include/boost/spirit/home/x3/directive/with.hpp @@ -1,5 +1,6 @@ /*============================================================================= Copyright (c) 2014 Joel de Guzman + Copyright (c) 2025 Nana Sakisaka Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,77 +11,201 @@ #include #include -namespace boost { namespace spirit { namespace x3 +#include +#include +#include + +namespace boost::spirit::x3 { - /////////////////////////////////////////////////////////////////////////// - // with directive injects a value into the context prior to parsing. - /////////////////////////////////////////////////////////////////////////// - template - struct with_value_holder - : unary_parser - { - typedef unary_parser base_type; - mutable T val; - constexpr with_value_holder(Subject const& subject, T&& val) - : base_type(subject) - , val(std::forward(val)) {} - }; - - template - struct with_value_holder - : unary_parser - { - typedef unary_parser base_type; - T& val; - constexpr with_value_holder(Subject const& subject, T& val) - : base_type(subject) - , val(val) {} - }; - template - struct with_directive - : with_value_holder, T> + struct with_directive; + + namespace detail { - typedef with_value_holder, T> base_type; - static bool const is_pass_through_unary = true; - static bool const handles_container = Subject::handles_container; - - typedef Subject subject_type; - - constexpr with_directive(Subject const& subject, T&& val) - : base_type(subject, std::forward(val)) {} - - template - bool parse(Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, Attribute& attr) const + template + struct with_directive_impl + : unary_parser> { + using base_type = unary_parser>; + mutable T val_; + + template + requires + std::is_constructible_v && + std::is_constructible_v + constexpr with_directive_impl(SubjectT&& subject, U&& val) + noexcept( + std::is_nothrow_constructible_v && + std::is_nothrow_constructible_v + ) + : base_type(std::forward(subject)) + , val_(std::forward(val)) + {} + }; + + template + struct with_directive_impl + : unary_parser> + { + using base_type = unary_parser>; + /* not mutable */ T const val_; + + template + requires + std::is_constructible_v && + std::is_constructible_v + constexpr with_directive_impl(SubjectT&& subject, U&& val) + noexcept( + std::is_nothrow_constructible_v && + std::is_nothrow_constructible_v + ) + : base_type(std::forward(subject)) + , val_(std::forward(val)) + {} + }; + + template + struct with_directive_impl + : unary_parser> + { + using base_type = unary_parser>; + T& val_; + + template + requires + std::is_constructible_v && + std::is_constructible_v + constexpr with_directive_impl(SubjectT&& subject, U& val) + noexcept(std::is_nothrow_constructible_v) + : base_type(std::forward(subject)) + , val_(val) + {} + }; + } // detail + + // `with` directive injects a value into the context prior to parsing. + // Holds lvalue references by reference, holds rvalue reference by value. + template + struct with_directive : detail::with_directive_impl + { + static_assert( + !std::is_rvalue_reference_v, + "`x3::with`: rvalue reference is prohibited to prevent dangling reference" + ); + + static_assert( + std::is_lvalue_reference_v || std::is_move_constructible_v, + "Passing an rvalue to `x3::with` requires the type to be move-constructible " + "so it can be stored by value." + ); + + using subject_type = Subject; + using id_type = ID; + using value_type = T; + using base_type = detail::with_directive_impl; + + static constexpr bool is_pass_through_unary = true; + static constexpr bool handles_container = Subject::handles_container; + + template + requires std::is_constructible_v + constexpr with_directive(SubjectT&& subject, U&& val) + noexcept(std::is_nothrow_constructible_v) + : base_type(std::forward(subject), std::forward(val)) + {} + + // The internal context type. This can be used to determine the composed + // context type used in `x3::parse`/`x3::phrase_parse`. It is required for + // the argument of `BOOST_SPIRIT_X3_INSTANTIATE`. + template + using context_t = context, Context>; + + template Se, typename Context, typename RContext, typename Attribute> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& context, RContext& rcontext, Attribute& attr) const + noexcept(is_nothrow_parsable_v, RContext, Attribute>) + { + static_assert(Parsable, RContext, Attribute>); return this->subject.parse( - first, last - , make_context(this->val, context) - , rcontext - , attr); + first, last, + x3::make_context(this->val_, context), + rcontext, attr + ); } - }; - - template - struct with_gen - { - T&& val; - template - constexpr with_directive::value_type, ID, T> - operator[](Subject const& subject) const + private: + using base_type::val_; + }; + + namespace detail + { + template + struct [[nodiscard]] with_gen { - return { as_parser(subject), std::forward(val) }; - } - }; + static_assert(!std::is_rvalue_reference_v); + T val; - template - constexpr with_gen with(T&& val) + template + [[nodiscard]] constexpr with_directive, ID, T> + operator[](Subject&& subject) && + noexcept( + is_parser_nothrow_castable_v && + std::is_nothrow_constructible_v< + with_directive, ID, T>, + as_parser_t, + T&& // lvalue or rvalue, forwarded + > + ) + { + // with rvalue `with_gen`, the held value can always be forwarded + return { as_parser(std::forward(subject)), std::forward(val) }; + } + + template + [[nodiscard]] constexpr with_directive, ID, T> + operator[](Subject&& subject) const& + noexcept( + is_parser_nothrow_castable_v && + std::is_nothrow_constructible_v< + with_directive, ID, T>, + as_parser_t, + T& // lvalue + > + ) + { + static_assert( + std::is_lvalue_reference_v || std::is_copy_constructible_v, + "When you have passed an rvalue to `x3::with` and saved the functor " + "as a local variable, it requires the held type to be copy-constructible. " + "If your type is move only, then apply `std::move` to your `x3::with(val)` " + "instance." + ); + return { as_parser(std::forward(subject)), val }; + } + }; + + template + struct with_fn + { + template + static constexpr with_gen operator()(T&& val) + noexcept( + std::is_lvalue_reference_v || + std::is_nothrow_move_constructible_v + ) + { + return { std::forward(val) }; + } + }; + } // detail + + inline namespace cpos { - return { std::forward(val) }; - } -}}} + // `with` directive injects a value into the context prior to parsing. + // Holds lvalue references by reference, holds rvalue reference by value. + template + inline constexpr detail::with_fn with{}; + } // cpos +} // boost::spirit::x3 #endif diff --git a/test/x3/with.cpp b/test/x3/with.cpp index 74247f555..0b3081e8f 100644 --- a/test/x3/with.cpp +++ b/test/x3/with.cpp @@ -9,7 +9,10 @@ #include +#include #include +#include +#include namespace x3 = boost::spirit::x3; @@ -33,27 +36,108 @@ struct my_rule_class } }; +namespace +{ + using boost::spirit::x3::rule; + using boost::spirit::x3::int_; + using boost::spirit::x3::with; + using boost::spirit::x3::_pass; + + template + constexpr auto value_equals = int_[([](auto& ctx) { + auto&& with_val = x3::get(ctx); + static_assert(std::same_as); + _pass(ctx) = with_val == _attr(ctx); + })]; + + struct move_only + { + move_only() = default; + move_only(move_only const&) = delete; + move_only(move_only&&) noexcept {} + move_only& operator=(move_only const&) = delete; + move_only& operator=(move_only&&) noexcept { return *this; } + }; + +} // anonymous + int main() { using spirit_test::test_attr; using spirit_test::test; - using boost::spirit::x3::rule; - using boost::spirit::x3::int_; - using boost::spirit::x3::with; - -// read from a mutable field is not allowed on these compilers -#if (!defined(_MSC_VER) || _MSC_VER >= 1910) && \ - (!defined(__clang__) || __clang_major__ >= 7) BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(with(0)['x']); -#endif + { constexpr int i = 0; BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(with(i)['x']); } - { // injecting data into the context in the grammar + // check various value categories + { + { + int i = 42; + BOOST_TEST(test("42", with(i)[value_equals])); + } + { + int const i = 42; + BOOST_TEST(test("42", with(i)[value_equals])); + } + { + int i = 42; + BOOST_TEST(test("42", with(std::move(i))[value_equals])); + } + { + int const i = 42; + BOOST_TEST(test("42", with(std::move(i))[value_equals])); + } + { + int i = 42; + auto with_gen = with(i); + BOOST_TEST(test("42", with_gen[value_equals])); + } + { + int const i = 42; + auto with_gen = with(i); + BOOST_TEST(test("42", with_gen[value_equals])); + } + { + int i = 42; + auto with_gen = with(std::move(i)); + BOOST_TEST(test("42", with_gen[value_equals])); + } + { + int const i = 42; + auto with_gen = with(std::move(i)); + BOOST_TEST(test("42", with_gen[value_equals])); + } + + // lvalue `move_only` + { + move_only mo; + (void)with(mo)[int_]; + } + { + move_only mo; + auto with_gen = with(mo); // passed-by-reference + (void)with_gen[int_]; // permitted, never copies + (void)std::move(with_gen)[int_]; + } + + // rvalue `move_only` + { + (void)with(move_only{})[int_]; + } + { + auto with_gen = with(move_only{}); + // (void)with_gen[int_]; // requires copy-constructible + (void)std::move(with_gen)[int_]; + } + } + + { + // injecting data into the context in the grammar int val = 0; auto r = rule() = '(' > int_ > ',' > int_ > ')' @@ -68,7 +152,8 @@ int main() BOOST_TEST(val == 2); } - { // injecting non-const lvalue into the context + { + // injecting non-const lvalue into the context int val = 0; auto const r = int_[([](auto& ctx){ x3::get(ctx) += x3::_attr(ctx); @@ -77,7 +162,8 @@ int main() BOOST_TEST(579 == val); } - { // injecting rvalue into the context + { + // injecting rvalue into the context auto const r1 = int_[([](auto& ctx){ x3::get(ctx) += x3::_attr(ctx); })]; @@ -90,7 +176,8 @@ int main() BOOST_TEST(106 == attr); } - { // injecting const/non-const lvalue and rvalue into the context + { + // injecting const/non-const lvalue and rvalue into the context struct functor { int operator()(int& val) { return val * 10; // non-const ref returns 10 * injected val