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.
This might actually be Undefined Behaviour but I didn't check the documentation.
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
boost::variant<mode, std::string> v;
And Live On Coliru
boost::variant<int, mode, std::string> v;
And then you do
const auto gen = mode_gen<std::back_insert_iterator<std::string> > | uint_ | string;
Same applies as with Qi: the proto-expressions hold rule operands by reference, and that means auto
is a bad idea:
Run your code with UBSan/ASan and use Valgring to catch errors like these, before they eat your customer's data.
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:
Make mode
a custom type:
#include <boost/spirit/include/karma.hpp>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
struct mode : std::string {
using std::string::string;
};
namespace karma = boost::spirit::karma;
template <typename Out = boost::spirit::ostream_iterator>
karma::rule<Out, mode()> mode_gen = "mode=\"" << karma::string << "\"";
int main() {
using Variant = boost::variant<mode, std::string, unsigned>;
Variant foo = std::string("foo"),
bar = mode("bar"),
i = 42;
for (Variant v : { foo, bar, i })
std::cout << "Output: " << format(mode_gen<> | karma::uint_ | karma::string, v) << "\n";
}
Prints
Output: foo
Output: mode="bar"
Output: 42
I couldn't make this work right away, so let me just point at a sample implementation:
#include <boost/serialization/strong_typedef.hpp>
std::string
You can use a hack:
namespace hack {
template <typename Char, typename Tag>
struct my_traits : std::char_traits<Char> {};
}
using mode = std::basic_string<char, hack::my_traits<char, struct ModeTag> >;
That still prints the same Live On Coliru
Output: foo
Output: mode="bar"
Output: 42
There are issues with your generator. Specifically, if your mode
value contains a quote, things will go awry. You might simply leverage ostream
:
struct mode : std::string {
using std::string::string;
friend std::ostream& operator<<(std::ostream& os, mode const& m) {
return os << "mode=" << std::quoted(m);
}
};
This way a simple
std::cout << mode("yo") << std::endl;
std::cout << mode("y\"!\"o") << std::endl;
would print Live On Coliru
mode="yo"
mode="y\"!\"o"
Which is considerably more elegant. It also means you can replace all of the karma grammar with karma::stream
:
#include <boost/spirit/include/karma.hpp>
#include <iostream>
#include <iomanip>
struct mode : std::string {
using std::string::string;
friend std::ostream& operator<<(std::ostream& os, mode const& m) {
return os << "mode=" << std::quoted(m);
}
};
int main() {
boost::variant<mode, std::string, unsigned>
foo = std::string("foo"),
bar = mode("bar"),
i = 42;
for (auto v : { foo, bar, i })
std::cout << "Output: " << karma::format(karma::stream, v) << "\n";
}
I LOVE IT when less and less code does more and more. But at this rate, one wonders why even use
karma
?
To make it shine with the my_traits
approach and your Tag type, take Argument Dependent Lookup to the max:
#include <boost/variant.hpp>
#include <iostream>
#include <iomanip>
namespace hack {
template <typename Char, typename Tag>
struct my_traits : std::char_traits<Char> {};
}
namespace mylib {
struct ModeTag{};
struct ValueTag{};
static inline std::ostream& operator<<(std::ostream& os, ModeTag) { return os << "mode"; }
static inline std::ostream& operator<<(std::ostream& os, ValueTag) { return os << "value"; }
template <typename Char, typename Tag>
static inline std::ostream& operator<<(std::ostream& os, hack::my_traits<Char, Tag>)
{ return os << Tag{}; }
template <typename Char, typename CharT, typename Alloc>
std::ostream& operator<<(std::ostream& os, std::basic_string<Char, CharT, Alloc> const& s) {
return os << CharT{} << "=" << std::quoted(s);
}
}
using mode = std::basic_string<char, hack::my_traits<char, struct mylib::ModeTag> >;
using value = std::basic_string<char, hack::my_traits<char, struct mylib::ValueTag> >;
int main() {
boost::variant<mode, value, unsigned>
foo = value("foo"),
bar = mode("bar"),
i = 42;
std::cout << foo << std::endl;
std::cout << bar << std::endl;
std::cout << i << std::endl;
}
It compiles 10x faster and prints:
value="foo"
mode="bar"
42