I am trying to parse a std::Variant
with a fusion-adapted Struct type that contains a single member. After several hours of trying to figure out the problem, I was able to reproduce the issue with this code:
struct TestStruct {
float value;
};
BOOST_FUSION_ADAPT_STRUCT(TestStruct, value)
typedef std::variant<TestStruct, std:string> TestVariant;
auto TestStructRule = x3::rule<struct test_struct, TestStruct>{} = x3::float_ >> ",";
auto TestVariantRule = x3::rule<struct test_variant, TestVariant>{} = TestStruct | "default" >> x3::attr(std::String{"default"});
This causes the following build error:
boost/boost/spirit/home/x3/support/traits/move_to.hpp:67:18: error: no viable overloaded '='
dest = std::move(fusion::front(src));
boost/boost/spirit/home/x3/support/traits/move_to.hpp:79:13: note: in instantiation of function template specialization 'boost::spirit::x3::traits::detail::move_to_plain<TestStruct, std::__1::variant<TestStruct, std::string> >' requested here
move_to_plain(std::forward<Source>(src), dest, is_single_element_sequence);
...
/Library/Developer/CommandLineTools/usr/include/c++/v1/variant:1214:12: note: candidate function not viable: no known conversion from 'typename remove_reference<float &>::type' (aka 'float') to 'const std::__1::variant<TestStruct, std::String>' for 1st argument
variant& operator=(const variant&) = default;
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/variant:1215:12: note: candidate function not viable: no known conversion from 'typename remove_reference<float &>::type' (aka 'float') to 'std::__1::variant<TestStruct, std::String>' for 1st argument
variant& operator=(variant&&) = default;
What I find baffling, is that if I add a second member to TestStruct
, the following code would actually compile and run correctly:
struct TestStruct {
float value1;
float value2; // <-- Added second member
};
BOOST_FUSION_ADAPT_STRUCT(TestStruct, value1, value2)
typedef std::variant<TestStruct, std:string> TestVariant;
auto TestStructRule = x3::rule<struct test_struct, TestStruct>{} = x3::float_ >> "," >> x3::float_ >> ",";
auto TestVariantRule = x3::rule<struct test_variant, TestVariant>{} = TestStruct | "default" >> x3::attr(std::String{"default"});
I've also read on SO, that there were known issues with fusion-adapted single field structs, when trying to parse these with older versions of Spirit.
What is the best way to fix (or go around) this issue with Spirit X3?
I hate to say, but this is a well-known limitation and it keeps cropping up. I've kind of given up on trying to get it fixed. It's an edge case that is usually easy to work-around or avoid.
See e.g.
In your cause I would probably avoid parsing into TestStruct
and rather parse into float
. Otherwise, parse it and propagate using a semantic action instead of automatic attribute propagation.
I tried and found it pretty hard to get over the hurdle in this specific case (it appears to be the "inverse" problem, where an already correctly matched rule exposing TestResult is still posing a problem down the road. Clearly the attribute synthesis rules are wrong again).
auto assign = [](auto& ctx) { _val(ctx) = _attr(ctx); };
Doing the attribute propagation manually:
auto test_struct
= x3::rule<struct test_struct, TestStruct>{}
= x3::float_ >> ",";
auto test_variant
= x3::rule<struct test_variant, TestVariant>{}
= test_struct [assign] | "default" >> x3::attr("default"s)[assign];
Works: Live On Coliru
Indeed, the "official" propagation would look more like¹
auto assign = [](auto& ctx) {
x3::traits::move_to(_attr(ctx), _val(ctx));
};
And it appears to not-work: Live On Coliru
¹ but not exactly, there is a lot of meta-programming conditions before that