I have a boost::variant consisting out of several types, including string type aliases and a string type. The string type aliases work as aspected with the boost::spirit::qi alternative parser, but the boost::spirit::karma alternative generator does not only work in a unwanted but also unexpected way, by not using the wanted string type alias generator rule, but also by not even using the built in string generator, when the variant includes the string type:
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>
using mode = std::string;
using alt_variant = boost::variant<mode, std::string, unsigned>;
using alt_variant_without_string = boost::variant<mode, unsigned>;
template <typename OutputIterator>
boost::spirit::karma::rule<OutputIterator, mode()>
mode_gen{
boost::spirit::karma::lit("mode=\"") <<
boost::spirit::karma::string
<< boost::spirit::karma::lit("\"")
};
int main(int argc, char *argv[]) {
alt_variant foo1{mode{"bar"}};
alt_variant_without_string foo2{mode{"bar"}};
std::string output;
using namespace boost::spirit::karma;
const auto gen = mode_gen<std::back_insert_iterator<std::string>> | uint_ | string;
boost::spirit::karma::generate(std::back_inserter(output), gen, foo1);
std::cout << "Output\"" << output << "\"\n"; //Output""
output.clear();
boost::spirit::karma::generate(std::back_inserter(output), gen, foo2);
std::cout << "Output\"" << output << "\"\n";//Output"mode="bar""
return 0;
}
Can somebody explain this behaviour, and how I get the wanted behaviour?
For the later one I guess, I have to get rid of all string type aliases and use explicit structs as types, but then I fall again in the ugly one member struct corner case. ( https://codereview.stackexchange.com/q/206259/95143 However, that the first output is not at least just "bar" i.e. that the string generator isn't used when the mode generator isn't either, looks like a bug to me i.e. I can't understand.
Where to start.
A. Unspecified Behaviour
A type alias does not create a new type. Therefore
typeid(std::string) == typeid(mode)and there is no way the variant can distinguish the two element types.The behaviour of Variant is unspecified. Compare: Live On Coliru
And Live On Coliru
B. Undefined Behaviour
And then you do
Same applies as with Qi: the proto-expressions hold rule operands by reference, and that means
autois a bad idea:Run your code with UBSan/ASan and use Valgring to catch errors like these, before they eat your customer's data.
The Problem
Your problem is you want expressive types that you can switch on. I think Java-ists like to call it Abstract Data Types. It's a lofty goal, and you can:
Solution 1
Make
modea custom type:Live On Coliru
Prints
Solution #2: Strong Typedef
I couldn't make this work right away, so let me just point at a sample implementation:
Solution #3: Distinguishing
std::stringYou can use a hack:
That still prints the same Live On Coliru
BONUS
There are issues with your generator. Specifically, if your
modevalue contains a quote, things will go awry. You might simply leverageostream:This way a simple
would print Live On Coliru
Which is considerably more elegant. It also means you can replace all of the karma grammar with
karma::stream:Live On Coliru
BONUS #2 - ADL It, and who needs Karma
To make it shine with the
my_traitsapproach and your Tag type, take Argument Dependent Lookup to the max:Live On Coliru
It compiles 10x faster and prints: