Where to start.
A. Unspecified Behaviour
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;
B. Undefined Behaviour
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.
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 mode
a custom type:
Live On Coliru
#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
Solution #2: Strong Typedef
I couldn't make this work right away, so let me just point at a sample implementation:
#include <boost/serialization/strong_typedef.hpp>
Solution #3: Distinguishing 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
BONUS
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
:
Live On Coliru
#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
?
BONUS #2 - ADL It, and who needs Karma
To make it shine with the my_traits
approach and your Tag type, take Argument Dependent Lookup to the max:
Live On Coliru
#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