Reputation: 2522
I am attempting to use a boost::variant
within a boost::optional
in a Karma generator. I've been able to reduce the problem to this:
using FooVariant = boost::variant<int>;
using FooOptional = boost::optional<FooVariant>;
template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
: boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
FooGenerator()
: FooGenerator::base_type(start_)
{
namespace bsk = boost::spirit::karma;
start_ = '[' << ( bsk::int_ | '*' ) << ']';
}
boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};
int main()
{
FooOptional fop1;
std::cout << boost::spirit::karma::format(FooGenerator<>(), fop1) << std::endl;
FooOptional fop2 = FooVariant{123};
std::cout << boost::spirit::karma::format(FooGenerator<>(), fop2) << std::endl;
}
The output here is:
[*]
[*]
Which is not what I was hoping for.
One of the first things I changed was more or less a sanity check by changing FooVariant
to: using FooVariant = int;
. This outputs:
[*]
[123]
And is what I want to see! So in my first code, the variant had only one type, so I tried adding a second type, just to see:
using FooVariant = boost::variant<int, double>;
...
start_ = '[' << ( ( bsk::int_ | bsk::double_ ) | '*' ) << ']';
But then we're back to:
[*]
[*]
Then I tried to add a rule specifically for the variant:
using FooVariant = boost::variant<int, double>;
using FooOptional = boost::optional<FooVariant>;
template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
: boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
FooGenerator()
: FooGenerator::base_type(start_)
{
namespace bsk = boost::spirit::karma;
foovar_ = (bsk::int_ | bsk::double_);
start_ = '[' << ( foovar_ | '*' ) << ']';
}
boost::spirit::karma::rule<OutputIt, FooVariant()> foovar_;
boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};
int main()
{
FooOptional fop1;
std::cout << boost::spirit::karma::format(FooGenerator<>(), fop1) << std::endl;
FooOptional fop2 = FooVariant{123};
std::cout << boost::spirit::karma::format(FooGenerator<>(), fop2) << std::endl;
}
Which gives a compilation error:
alternative_function.hpp:127:34: error: no member named 'is_compatible' in
'boost::spirit::traits::compute_compatible_component<boost::variant<int, double>, boost::optional<boost::variant<int, double> >, boost::spirit::karma::domain>'
if (!component_type::is_compatible(spirit::traits::which(attr_)))
~~~~~~~~~~~~~~~~^
It looks like the template generation is trying to make sure that the boost::variant
and boost::optional
are compatible but for me the question is "why is it trying to make sure they're compatible at all?"
How can I make this work?
Upvotes: 3
Views: 232
Reputation: 393114
I told you in the other answer how I'd handle this: Boost Karma: generate default text when boost::optional is unset
Not only does that sidestep the issue, it also simplifies the AST/data types.
Now, since you're forcing me¹, I dug in. The problem arises from the fact that foovar_ | '|'
is an alternative-expression and somehow it validates that the attribute must be compatible. During that check there is the assumption that
a
has attribute attr(a)
and b
has attribute attr(b)
thena | b
should have variant<attr(a), attr(b)
It does do some logic checks (like when attr(a) == attr(b)
) but not the logic check that if attr(b)
is unused_type
then it should be compatible with optional<attr(a)>
too instead of just with variant<attr(a), unused_type>
or even variant<attr(a)>
.
Now, the why doesn't interest me that much². So, here's how you can force things.
foovar_ = bsk::int_ | bsk::double_;
fooopt_ = (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_;
start_ = '[' << fooopt_ << ']';
The key is to encode it as something other than an alternative-expression. Here we simply trust -foovar_
to say something, or not. In addition, we also do
bsk::eps(is_initialized_(bsk::_val)) | '*'
meaning: If the variant is initialized, that's all, otherwise, generate '*'
.
Now you don't strictly need the third rule, if you like write-only code, you can just write:
start_ = '[' << (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_ << ']';
Oh, I almost forget:
struct is_initialized_f {
template<typename T>
bool operator()(boost::optional<T> const& opt) const { return opt.is_initialized(); }
};
boost::phoenix::function<is_initialized_f> is_initialized_;
Implements that helper Phoenix actor to check the initialization status.
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
using FooVariant = boost::variant<int, double>;
using FooOptional = boost::optional<FooVariant>;
template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
: boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
FooGenerator()
: FooGenerator::base_type(start_)
{
namespace bsk = boost::spirit::karma;
namespace phx = boost::phoenix;
foovar_ = bsk::int_ | bsk::double_;
//fooopt_ = (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_;
start_ = '[' << (bsk::eps(is_initialized_(bsk::_val)) | '*') << -foovar_ << ']';
}
private:
struct is_initialized_f {
template<typename T>
bool operator()(boost::optional<T> const& opt) const { return opt.is_initialized(); }
};
boost::phoenix::function<is_initialized_f> is_initialized_;
boost::spirit::karma::rule<OutputIt, FooVariant()> foovar_;
//boost::spirit::karma::rule<OutputIt, FooOptional()> fooopt_;
boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};
int main()
{
for (FooOptional fop : { FooOptional{}, {FooVariant{123}}, {FooVariant{3.14}} }) {
if (std::cout << boost::spirit::karma::format(FooGenerator<>(), fop))
std::cout << "\n";
else
{
std::cout.clear();
std::cout << "#Error\n";
}
}
}
Prints
[*]
[123]
[3.14]
¹ joke
² we'll get non-authoritative answers that rhyme with "someone forgot" or "it's a regression"
Upvotes: 4