Related
I have an application with several boost::variants which share many of the fields. I would like to be able to compose these visitors into visitors for "larger" variants without copying and pasting a bunch of code. It seems straightforward to do this for non-recursive variants, but once you have a recursive one, the self-references within the visitor (of course) point to the wrong class. To make this concrete (and cribbing from the boost::variant docs):
#include "boost/variant.hpp"
#include <iostream>
struct add;
struct sub;
template <typename OpTag> struct binop;
typedef boost::variant<
int
, boost::recursive_wrapper< binop<add> >
, boost::recursive_wrapper< binop<sub> >
> expression;
template <typename OpTag>
struct binop
{
expression left;
expression right;
binop( const expression & lhs, const expression & rhs )
: left(lhs), right(rhs)
{
}
};
// Add multiplication
struct mult;
typedef boost::variant<
int
, boost::recursive_wrapper< binop<add> >
, boost::recursive_wrapper< binop<sub> >
, boost::recursive_wrapper< binop<mult> >
> mult_expression;
class calculator : public boost::static_visitor<int>
{
public:
int operator()(int value) const
{
return value;
}
int operator()(const binop<add> & binary) const
{
return boost::apply_visitor( *this, binary.left )
+ boost::apply_visitor( *this, binary.right );
}
int operator()(const binop<sub> & binary) const
{
return boost::apply_visitor( *this, binary.left )
- boost::apply_visitor( *this, binary.right );
}
};
class mult_calculator : public boost::static_visitor<int>
{
public:
int operator()(int value) const
{
return value;
}
int operator()(const binop<add> & binary) const
{
return boost::apply_visitor( *this, binary.left )
+ boost::apply_visitor( *this, binary.right );
}
int operator()(const binop<sub> & binary) const
{
return boost::apply_visitor( *this, binary.left )
- boost::apply_visitor( *this, binary.right );
}
int operator()(const binop<mult> & binary) const
{
return boost::apply_visitor( *this, binary.left )
* boost::apply_visitor( *this, binary.right );
}
};
// I'd like something like this to compile
// class better_mult_calculator : public calculator
// {
// public:
// int operator()(const binop<mult> & binary) const
// {
// return boost::apply_visitor( *this, binary.left )
// * boost::apply_visitor( *this, binary.right );
// }
// };
int main(int argc, char **argv)
{
// result = ((7-3)+8) = 12
expression result(binop<add>(binop<sub>(7,3), 8));
assert( boost::apply_visitor(calculator(),result) == 12 );
std::cout << "Success add" << std::endl;
// result2 = ((7-3)+8)*2 = 12
mult_expression result2(binop<mult>(binop<add>(binop<sub>(7,3), 8),2));
assert( boost::apply_visitor(mult_calculator(),result2) == 24 );
std::cout << "Success mult" << std::endl;
}
I would really like something like that commented out better_mult_expression to compile (and work) but it doesn't -- because the this pointers within the base calculator visitor don't reference mult_expression, but expression.
Does anyone have suggestions for overcoming this or am I just barking down the wrong tree?
Firstly, I'd suggest the variant to include all possible node types, not distinguishing between mult and expression. This distinction makes no sense at the AST level, only at a parser stage (if you implement operator precedence in recursive/PEG fashion).
Other than that, here's a few observations:
if you encapsulate the apply_visitor dispatch into your evaluation functor you can reduce the code duplication by a big factor
your real question seems not to be about composing variants, but composing visitors, more specifically, by inheritance.
You can use using to pull inherited overloads into scope for overload resolution, so this might be the most direct answer:
Live On Coliru
struct better_mult_calculator : calculator {
using calculator::operator();
auto operator()(const binop<mult>& binary) const
{
return boost::apply_visitor(*this, binary.left) *
boost::apply_visitor(*this, binary.right);
}
};
IMPROVING!
Starting from that listing let's shave off some noise!
remove unncessary AST distinction (-40 lines, down to 55 lines of code)
generalize the operations; the <functional> header comes standard with these:
namespace AST {
template <typename> struct binop;
using add = binop<std::plus<>>;
using sub = binop<std::minus<>>;
using mult = binop<std::multiplies<>>;
using expr = boost::variant<int,
recursive_wrapper<add>,
recursive_wrapper<sub>,
recursive_wrapper<mult>>;
template <typename> struct binop { expr left, right; };
} // namespace AST
Now the entire calculator can be:
struct calculator : boost::static_visitor<int> {
int operator()(int value) const { return value; }
template <typename Op>
int operator()(AST::binop<Op> const& binary) const {
return Op{}(boost::apply_visitor(*this, binary.left),
boost::apply_visitor(*this, binary.right));
}
};
Here your variant can add arbitrary operations without even needing to touch the calculator.
Live Demo, 43 Lines Of Code
Like I mentioned starting off, encapsulate visitation!
struct Calculator {
template <typename... T> int operator()(boost::variant<T...> const& v) const {
return boost::apply_visitor(*this, v);
}
template <typename T>
int operator()(T const& lit) const { return lit; }
template <typename Op>
int operator()(AST::binop<Op> const& bin) const {
return Op{}(operator()(bin.left), operator()(bin.right));
}
};
Now you can just call your calculator, like intended:
Calculator calc;
auto result1 = calc(e1);
It will work when you extend the variant with operatios or even other literal types (like e.g. double). It will even work, regardless of whether you pass it an incompatible variant type that holds a subset of the node types.
To finish that off for maintainability/readability, I'd suggest making operator() only a dispatch function:
Full Demo
Live On Coliru
#include <boost/variant.hpp>
#include <iostream>
namespace AST {
using boost::recursive_wrapper;
template <typename> struct binop;
using add = binop<std::plus<>>;
using sub = binop<std::minus<>>;
using mult = binop<std::multiplies<>>;
using expr = boost::variant<int,
recursive_wrapper<add>,
recursive_wrapper<sub>,
recursive_wrapper<mult>>;
template <typename> struct binop { expr left, right; };
} // namespace AST
struct Calculator {
auto operator()(auto const& v) const { return call(v); }
private:
template <typename... T> int call(boost::variant<T...> const& v) const {
return boost::apply_visitor(*this, v);
}
template <typename T>
int call(T const& lit) const { return lit; }
template <typename Op>
int call(AST::binop<Op> const& bin) const {
return Op{}(call(bin.left), call(bin.right));
}
};
int main()
{
using namespace AST;
std::cout << std::boolalpha;
auto sub_expr = add{sub{7, 3}, 8};
expr e1 = sub_expr;
expr e2 = mult{sub_expr, 2};
Calculator calc;
auto result1 = calc(e1);
std::cout << "result1: " << result1 << " Success? " << (12 == result1) << "\n";
// result2 = ((7-3)+8)*2 = 12
auto result2 = calc(e2);
std::cout << "result2: " << result2 << " Success? " << (24 == result2) << "\n";
}
Still prints
result1: 12 Success? true
result2: 24 Success? true
I need to load these values from INI file and print them in the application using C++ Boost Library. The sections have duplicate names. I have been restricted to using C++ Boost Library only.
numColors = 4
boardSize = 11
numSnails = 2
[initialization]
id = 0
row = 3
col = 4
orientation = 0
[initialization]
id = 1
row = 5
col = 0
orientation = 1
[color]
id = 0
nextColor = 1
deltaOrientation = +2
[color]
id = 1
nextColor = 2
deltaOrientation = +1
[color]
id = 2
nextColor = 3
deltaOrientation = -2
[color]
id = 3
nextColor = 0
deltaOrientation = -1
I figured that for a random passer-by the Boost Spirit answer might seem complex/overkill.
I wanted to try again, since it's 2021, whether with C++17, we can do a reasonable job with just the standard library.
Turns out it's a lot more work. The Qi implementation takes 86 lines of code, but the standard library implementation takes 136 lines. Also, it took me a lot longer (several hours) to debug/write. In particular it was hard to get '=', '[', ']' as token boundaries with std::istream&. I used the ctype facet approach from this answer: How do I iterate over cin line by line in C++?
I did leave in the DebugPeeker (20 lines) so you can perhaps understand it yourself.
Short Expo
The top level parse function looks sane and shows what I wanted to achieve: natural std::istream extraction:
static Ast::File std_parse_game(std::string_view input) {
std::istringstream iss{std::string(input)};
using namespace Helpers;
if (Ast::File parsed; iss >> parsed)
return parsed;
throw std::runtime_error("Unable to parse game");
}
All the rest lives in namespace Helpers:
static inline std::istream& operator>>(std::istream& is, Ast::File& v) {
for (section s; is >> s;) {
if (s.name == "parameters")
is >> v.parameters;
else if (s.name == "initialization")
is >> v.initializations.emplace_back();
else if (s.name == "color")
is >> v.colors.emplace_back();
else
is.setstate(std::ios::failbit);
}
if (is.eof())
is.clear();
return is;
}
So far, this is paying out well. The different section types are similar:
static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
return is
>> entry{"numColors", v.numColors}
>> entry{"boardSize", v.boardSize}
>> entry{"numSnails", v.numSnails};
}
static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
return is
>> entry{"id", v.id}
>> entry{"row", v.row}
>> entry{"col", v.col}
>> entry{"orientation", v.orientation};
}
static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
return is
>> entry{"id", v.id}
>> entry{"nextColor", v.nextColor}
>> entry{"deltaOrientation", v.deltaOrientation};
}
Now, if all was plain sailing like this, I would not be recommending Spirit. Now we get into conditional parsings.
The entry{"name", value} formulations use a "manipulator type":
template <typename T> struct entry {
entry(std::string name, T& into) : _name(name), _into(into) {}
std::string _name;
T& _into;
friend std::istream& operator>>(std::istream& is, entry e) {
return is >> expect{e._name} >> expect{'='} >> e._into;
}
};
Similarly, sections are using expect and token:
struct section {
std::string name;
friend std::istream& operator>>(std::istream& is, section& s) {
if (is >> expect('['))
return is >> token{s.name} >> expect{']'};
return is;
}
};
The conditional is important to be able to detect EOF without putting the stream into a hard failed mode (is.bad() != is.fail()).
expect is built on top of token:
template <typename T> struct expect {
expect(T expected) : _expected(expected) {}
T _expected;
friend std::istream& operator>>(std::istream& is, expect const& e) {
if (T actual; is >> token{actual})
if (actual != e._expected)
is.setstate(std::ios::failbit);
return is;
}
};
You will notice that there's much less error information. We just make the
stream fail() in case an expected token is not found.
Here's where the real complexity comes. I didn't want to parse character by
character. But reading std::string using operator>> would only stop on
whitespace, meaning that a section name would "eat" the bracket: parameters]
instead of parameters, and keys might eat the = character if there was no
separating space.
In the answer linked above we learn how to build our own character
classification locale facet:
// make sure =,[,] break tokens
struct mytoken_ctype : std::ctype<char> {
static auto const* get_table() {
static std::vector rc(table_size, std::ctype_base::mask());
rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
std::ctype_base::space;
// crucial for us:
rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
return rc.data();
}
mytoken_ctype() : std::ctype<char>(get_table()) {}
};
And then we need to use it but only if we parse a std::string token. That
way, if we expect('=') it will not skip over '=' because our facet calls it whitespace...
template <typename T> struct token {
token(T& into) : _into(into) {}
T& _into;
friend std::istream& operator>>(std::istream& is, token const& t) {
std::locale loc = is.getloc();
if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
}
try { is >> t._into; is.imbue(loc); }
catch (...) { is.imbue(loc); throw; }
return is;
}
};
I tried to keep it pretty condensed. Had I used proper formatting, we would
have had more lines of code still :)
DEMO AND TEST
I used the same Ast types, so it made sense to test both implementations and
compare the results for equality.
NOTES:
On Compiler Explorer so we can enjoy libfmt for easy output
For comparison I used one C++20 feature to get compiler generated operator==
Live On Compiler Explorer
#include <boost/spirit/home/qi.hpp>
#include <boost/fusion/include/io.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;
namespace Ast {
using Id = unsigned;
using Size = uint16_t; // avoiding char types for easy debug/output
using Coord = Size;
using ColorNumber = Size;
using Orientation = Size;
using Delta = signed;
struct Parameters {
Size numColors{}, boardSize{}, numSnails{};
bool operator==(Parameters const&) const = default;
};
struct Initialization {
Id id;
Coord row;
Coord col;
Orientation orientation;
bool operator==(Initialization const&) const = default;
};
struct Color {
Id id;
ColorNumber nextColor;
Delta deltaOrientation;
bool operator==(Color const&) const = default;
};
struct File {
Parameters parameters;
std::vector<Initialization> initializations;
std::vector<Color> colors;
bool operator==(File const&) const = default;
};
using boost::fusion::operator<<;
template <typename T>
static inline std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) {
return os << fmt::format("vector<{}>{}",
boost::core::demangle(typeid(T).name()), v);
}
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Parameters, numColors, boardSize, numSnails)
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, parameters, initializations, colors)
template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
GameParser() : GameParser::base_type(start) {
using namespace qi;
start = skip(blank)[file];
auto section = [](const std::string& name) {
return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
};
auto required = [](const std::string& name, auto value) {
return copy(lexeme[eps > lit(name)] > '=' > value >
(+eol | eoi));
};
file = parameters >
*initialization >
*color >
eoi; // must reach end of input
parameters = section("parameters") >
required("numColors", _size) >
required("boardSize", _size) >
required("numSnails", _size);
initialization = section("initialization") >
required("id", _id) >
required("row", _coord) >
required("col", _coord) >
required("orientation", _orientation);
color = section("color") >
required("id", _id) >
required("nextColor", _colorNumber) >
required("deltaOrientation", _delta);
BOOST_SPIRIT_DEBUG_NODES((file)(parameters)(initialization)(color))
}
private:
using Skipper = qi::blank_type;
qi::rule<It, Ast::File()> start;
qi::rule<It, Ast::File(), Skipper> file;
// sections
qi::rule<It, Ast::Parameters(), Skipper> parameters;
qi::rule<It, Ast::Initialization(), Skipper> initialization;
qi::rule<It, Ast::Color(), Skipper> color;
// value types
qi::uint_parser<Ast::Id> _id;
qi::uint_parser<Ast::Size> _size;
qi::uint_parser<Ast::Coord> _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta> _delta;
};
static Ast::File qi_parse_game(std::string_view input) {
using SVI = std::string_view::const_iterator;
static const GameParser<SVI> parser{};
try {
Ast::File parsed;
if (qi::parse(input.begin(), input.end(), parser, parsed)) {
return parsed;
}
throw std::runtime_error("Unable to parse game");
} catch (qi::expectation_failure<SVI> const& ef) {
std::ostringstream oss;
auto where = ef.first - input.begin();
auto sol = 1 + input.find_last_of("\r\n", where);
auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
auto col = 1 + where - sol;
auto llen = input.substr(sol).find_first_of("\r\n");
oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
<< " note: " << input.substr(sol, llen) << "\n"
<< " note:" << std::setw(col) << "" << "^--- here";
throw std::runtime_error(oss.str());
}
}
namespace Helpers {
struct DebugPeeker {
DebugPeeker(std::istream& is, int line) : is(is), line(line) { dopeek(); }
~DebugPeeker() { dopeek(); }
private:
std::istream& is;
int line;
void dopeek() const {
std::char_traits<char> t;
auto ch = is.peek();
std::cerr << "DEBUG " << line << " Peek: ";
if (std::isgraph(ch))
std::cerr << "'" << t.to_char_type(ch) << "'";
else
std::cerr << "<" << ch << ">";
std::cerr << " " << std::boolalpha << is.good() << "\n";
}
};
#define DEBUG_PEEK(is) // Peeker _peek##__LINE__(is, __LINE__);
// make sure =,[,] break tokens
struct mytoken_ctype : std::ctype<char> {
static auto const* get_table() {
static std::vector rc(table_size, std::ctype_base::mask());
rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
std::ctype_base::space;
// crucial for us:
rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
return rc.data();
}
mytoken_ctype() : std::ctype<char>(get_table()) {}
};
template <typename T> struct token {
token(T& into) : _into(into) {}
T& _into;
friend std::istream& operator>>(std::istream& is, token const& t) {
DEBUG_PEEK(is);
std::locale loc = is.getloc();
if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
}
try { is >> t._into; is.imbue(loc); }
catch (...) { is.imbue(loc); throw; }
return is;
}
};
template <typename T> struct expect {
expect(T expected) : _expected(expected) {}
T _expected;
friend std::istream& operator>>(std::istream& is, expect const& e) {
DEBUG_PEEK(is);
if (T actual; is >> token{actual})
if (actual != e._expected)
is.setstate(std::ios::failbit);
return is;
}
};
template <typename T> struct entry {
entry(std::string name, T& into) : _name(name), _into(into) {}
std::string _name;
T& _into;
friend std::istream& operator>>(std::istream& is, entry e) {
DEBUG_PEEK(is);
return is >> expect{e._name} >> expect{'='} >> e._into;
}
};
struct section {
std::string name;
friend std::istream& operator>>(std::istream& is, section& s) {
DEBUG_PEEK(is);
if (is >> expect('['))
return is >> token{s.name} >> expect{']'};
return is;
}
};
static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
DEBUG_PEEK(is);
return is
>> entry{"numColors", v.numColors}
>> entry{"boardSize", v.boardSize}
>> entry{"numSnails", v.numSnails};
}
static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
DEBUG_PEEK(is);
return is
>> entry{"id", v.id}
>> entry{"row", v.row}
>> entry{"col", v.col}
>> entry{"orientation", v.orientation};
}
static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
DEBUG_PEEK(is);
return is
>> entry{"id", v.id}
>> entry{"nextColor", v.nextColor}
>> entry{"deltaOrientation", v.deltaOrientation};
}
static inline std::istream& operator>>(std::istream& is, Ast::File& v) {
DEBUG_PEEK(is);
for (section s; is >> s;) {
if (s.name == "parameters")
is >> v.parameters;
else if (s.name == "initialization")
is >> v.initializations.emplace_back();
else if (s.name == "color")
is >> v.colors.emplace_back();
else
is.setstate(std::ios::failbit);
}
if (is.eof())
is.clear();
return is;
}
}
static Ast::File std_parse_game(std::string_view input) {
std::istringstream iss{std::string(input)};
using namespace Helpers;
if (Ast::File parsed; iss >> parsed)
return parsed;
throw std::runtime_error("Unable to parse game");
}
std::string read_file(const std::string& name) {
std::ifstream ifs(name);
return std::string(std::istreambuf_iterator<char>(ifs), {});
}
int main() {
std::string const game_save = read_file("input.txt");
Ast::File g1, g2;
try {
std::cout << "Qi: " << (g1 = qi_parse_game(game_save)) << "\n";
} catch (std::exception const& e) { std::cerr << e.what() << "\n"; }
try {
std::cout << "std: " << (g2 = std_parse_game(game_save)) << "\n";
} catch (std::exception const& e) { std::cerr << e.what() << "\n"; }
std::cout << "Equal: " << std::boolalpha << (g1 == g2) << "\n";
}
To my great relief, the parsers agree on the data:
Qi: ((4 11 2)
vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
std: ((4 11 2)
vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
Equal: true
Summary/Conclusion
Although this answer is "standard" and "portable" it has some drawbacks.
For example it is certainly not easier to get right, it has virtually no debug options or error reporting, it doesn't validate the input format as much. E.g. it will still read this unholy mess and accept it:
[parameters] numColors=999 boardSize=999 numSnails=999
[color] id=0 nextColor=1 deltaOrientation=+2 [color] id=1 nextColor=2
deltaOrientation=+1 [
initialization] id=1 row=5 col=0 orientation=1
[color] id=2 nextColor=3 deltaOrientation=-2
[parameters] numColors=4 boardSize=11 numSnails=2
[color] id=3 nextColor=0 deltaOrientation=-1
[initialization] id=0 row=3 col=4 orientation=0
If your input format is not stable and computer-written, I would highly recommend against the standard-library approach because it will lead to hard-to-diagnose problems and just horrible UX (don't make your users want to throw their computer out of the window because of unhelpful error messages like "Unable to parse game data").
Otherwise, you might. For one thing, it'll be faster to compile.
What It Isn't
In short, this is not INI format at all. It just very loosely resembles it. Which is nice.
What Is It Instead?
You don't specify a lot, so I'm going to make assumptions.
I'm going to, for simplicity, assume that
initialization sections precede color sections
keys in like sections have the same order always
all keys shown are mandatory in like sections
the deltas are signed integral values (positive sign being optional)
all other values are non-negative integral numbers
whitespace is not significant
case is significant
all numbers are in decimal form (regardless of leading zeros)
Non-essential deductions (could be used to add more validation):
the number of of initializations = numSnails
the board size dictates row and col are in [0, boardSize)
Data Structures
To represent the file, I'd make:
namespace Ast {
struct Initialization {
unsigned id, row, col, orientation;
};
struct Color {
unsigned id, nextColor;
int deltaOrientation;
};
struct File {
unsigned numColors, boardSize, numSnails;
std::vector<Initialization> initializations;
std::vector<Color> colors;
};
}
That's the simplest I can think of.
Parsing It
Is a nice job for Boost Spirit. If we adapt the data structures as Fusion Sequences:
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, numColors, boardSize, numSnails,
initializations, colors)
We can basically let the parser "write itself":
template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
GameParser() : GameParser::base_type(start) {
using namespace qi;
start = skip(blank)[file];
auto section = [](std::string name) {
return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
};
auto required = [](std::string name) {
return copy(lexeme[eps > lit(name)] > '=' > auto_ >
(+eol | eoi));
};
file =
required("numColors") >
required("boardSize") >
required("numSnails") >
*initialization >
*color >
eoi; // must reach end of input
initialization = section("initialization") >
required("id") >
required("row") >
required("col") >
required("orientation");
color = section("color") >
required("id") >
required("nextColor") >
required("deltaOrientation");
BOOST_SPIRIT_DEBUG_NODES((file)(initialization)(color))
}
private:
using Skipper = qi::blank_type;
qi::rule<It, Ast::File()> start;
qi::rule<It, Ast::File(), Skipper> file;
qi::rule<It, Ast::Initialization(), Skipper> initialization;
qi::rule<It, Ast::Color(), Skipper> color;
};
Because of the many assumptions we've made we littered the place with expectation points (operator> sequences, instead of operator>>). This means we get "helpful" error messages on invalid input, like
Expected: nextColor
Expected: =
Expected: <eoi>
See also BONUS section below that improves this a lot
Testing/Live Demo
Testing it, we will read the file first and then parse it using that parser:
std::string read_file(std::string name) {
std::ifstream ifs(name);
return std::string(std::istreambuf_iterator<char>(ifs), {});
}
static Ast::File parse_game(std::string_view input) {
using SVI = std::string_view::const_iterator;
static const GameParser<SVI> parser{};
try {
Ast::File parsed;
if (qi::parse(input.begin(), input.end(), parser, parsed)) {
return parsed;
}
throw std::runtime_error("Unable to parse game");
} catch (qi::expectation_failure<SVI> const& ef) {
std::ostringstream oss;
oss << "Expected: " << ef.what_;
throw std::runtime_error(oss.str());
}
}
A lot could be improved, but for now it works and parses your input:
Live On Coliru
int main() {
std::string game_save = read_file("input.txt");
Ast::File data = parse_game(game_save);
}
The absense of output means success.
BONUS
Some improvements, instead of using auto_ to generate the right parser for the type, we can make that explicit:
namespace Ast {
using Id = unsigned;
using Size = uint8_t;
using Coord = Size;
using ColorNumber = Size;
using Orientation = Size;
using Delta = signed;
struct Initialization {
Id id;
Coord row;
Coord col;
Orientation orientation;
};
struct Color {
Id id;
ColorNumber nextColor;
Delta deltaOrientation;
};
struct File {
Size numColors{}, boardSize{}, numSnails{};
std::vector<Initialization> initializations;
std::vector<Color> colors;
};
} // namespace Ast
And then in the parser define the analogous:
qi::uint_parser<Ast::Id> _id;
qi::uint_parser<Ast::Size> _size;
qi::uint_parser<Ast::Coord> _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta> _delta;
Which we then use e.g.:
initialization = section("initialization") >
required("id", _id) >
required("row", _coord) >
required("col", _coord) >
required("orientation", _orientation);
Now we can improve the error messages to be e.g.:
input.txt:2:13 Expected: <unsigned-integer>
note: boardSize = (11)
note: ^--- here
Or
input.txt:16:19 Expected: <alternative><eol><eoi>
note: nextColor = 1 deltaOrientation = +2
note: ^--- here
Full Code, Live On Coliru
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/home/qi.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace Ast {
using Id = unsigned;
using Size = uint8_t;
using Coord = Size;
using ColorNumber = Size;
using Orientation = Size;
using Delta = signed;
struct Initialization {
Id id;
Coord row;
Coord col;
Orientation orientation;
};
struct Color {
Id id;
ColorNumber nextColor;
Delta deltaOrientation;
};
struct File {
Size numColors{}, boardSize{}, numSnails{};
std::vector<Initialization> initializations;
std::vector<Color> colors;
};
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, numColors, boardSize, numSnails,
initializations, colors)
template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
GameParser() : GameParser::base_type(start) {
using namespace qi;
start = skip(blank)[file];
auto section = [](const std::string& name) {
return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
};
auto required = [](const std::string& name, auto value) {
return copy(lexeme[eps > lit(name)] > '=' > value >
(+eol | eoi));
};
file =
required("numColors", _size) >
required("boardSize", _size) >
required("numSnails", _size) >
*initialization >
*color >
eoi; // must reach end of input
initialization = section("initialization") >
required("id", _id) >
required("row", _coord) >
required("col", _coord) >
required("orientation", _orientation);
color = section("color") >
required("id", _id) >
required("nextColor", _colorNumber) >
required("deltaOrientation", _delta);
BOOST_SPIRIT_DEBUG_NODES((file)(initialization)(color))
}
private:
using Skipper = qi::blank_type;
qi::rule<It, Ast::File()> start;
qi::rule<It, Ast::File(), Skipper> file;
qi::rule<It, Ast::Initialization(), Skipper> initialization;
qi::rule<It, Ast::Color(), Skipper> color;
qi::uint_parser<Ast::Id> _id;
qi::uint_parser<Ast::Size> _size;
qi::uint_parser<Ast::Coord> _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta> _delta;
};
std::string read_file(const std::string& name) {
std::ifstream ifs(name);
return std::string(std::istreambuf_iterator<char>(ifs), {});
}
static Ast::File parse_game(std::string_view input) {
using SVI = std::string_view::const_iterator;
static const GameParser<SVI> parser{};
try {
Ast::File parsed;
if (qi::parse(input.begin(), input.end(), parser, parsed)) {
return parsed;
}
throw std::runtime_error("Unable to parse game");
} catch (qi::expectation_failure<SVI> const& ef) {
std::ostringstream oss;
auto where = ef.first - input.begin();
auto sol = 1 + input.find_last_of("\r\n", where);
auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
auto col = 1 + where - sol;
auto llen = input.substr(sol).find_first_of("\r\n");
oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
<< " note: " << input.substr(sol, llen) << "\n"
<< " note:" << std::setw(col) << "" << "^--- here";
throw std::runtime_error(oss.str());
}
}
int main() {
std::string game_save = read_file("input.txt");
try {
Ast::File data = parse_game(game_save);
} catch (std::exception const& e) {
std::cerr << e.what() << "\n";
}
}
Look here for various failure modes and BOOST_SPIRIT_DEBUG ouput:
I want to create a generalized heap data structure, and facing an issue with passing template comparator.
template<typename T, typename C = less<T> > class Heap{
vector<T> *heap;
public:
Heap(vector<T> *arr){
heap = new vector<T> (arr->begin(), arr->end());
build_heap();
}
void build_heap(){
size_t n = heap->size();
for (size_t i=(n-1)/2; i>=0; i--){
shiftDown(i);
}
}
void shiftDown(size_t i){ /// heap logic
while(i < heap->size()){
size_t child = 2*i+1;
// int min_ind = 2*i+1;
if(child >= heap->size())
return;
if(child+1 < heap->size()){
if( C(heap->at(child+1),heap->at(child)) ){ // <----- using C as comparator
child++;
}
}
if( C(heap->at(child), heap->at(i)) ){ // <----- using C as comparator
swap(heap->at(child), heap->at(i));
i = child;
}
else
break;
}
}
};
int main(){
vector<int> v={8,7,6,5,4,3,2,1};
Heap<int, less<int> > heap(&v);
}
error
heap.cpp: In instantiation of ‘void Heap<T, C>::shiftDown(size_t) [with T = int; C = std::less<int>; size_t = long unsigned int]’:
heap.cpp:15:4: required from ‘void Heap<T, C>::build_heap() [with T = int; C = std::less<int>]’
heap.cpp:10:3: required from ‘Heap<T, C>::Heap(std::vector<_Tp>*) [with T = int; C = std::less<int>]’
heap.cpp:49:34: required from here
heap.cpp:32:9: error: no matching function for call to ‘std::less<int>::less(__gnu_cxx::__alloc_traits<std::allocator<int>, int>::value_type&, __gnu_cxx::__alloc_traits<std::allocator<int>, int>::value_type&)’
32 | if( C(heap->at(child+1),heap->at(child)) ){
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
detailed error
i'm following same syntex of declaration as stl c++ do, still i'm getting error. please help me out.
template<typename T, typename C = less<T> > class Heap;
any help or pointer to help is appreciated. thank you.
template<class T>
class Comparator{
bool operator()(const T &a, const T &b){
...
// returns logic
}
}
template<class T, class Comp >
class AnyClass{
public:
...
void function(){
// code ...
Comp<T>()(obj1, obj2);
}
...
}
calling sytex :
...
AnyClass *obj = new AnyClass<Type , Comparator>();
obj.function()
...
passing Comparator to templated class and when we need to compare objects
we create a functional object and call operator() with args to compare.
In question, that object is less<int>.
Comp<T>()(obj1, obj2);
This little program has to write an xml file.
Building the code I get the following error:
K:\Sergio\cpp\xml\sergio\cbp6s\main.cpp|32|error: 'base_tag' is an inaccessible base of 'tag'
In short, I have two classes derived from base_tag (xml_declaration and tag) and I want insert (or emplace) in a std::map some std::pair elements.
Building, the first insert works (std::pair<order::declaration, xml_declaration>), but the second fails (std::pair<order::root, tag_object>).
Between the object are derived from base_tag
Where am I wrong?
File tag.hpp :
#ifndef _TAG_HPP_
#define _TAG_HPP_
#include <string>
#include <vector>
#include <utility>
enum class tag_type
{
closing = -1,
autoclosed = 0,
opening = 1
};
using attribute = std::pair<std::string, std::string>;
class base_tag
{
public:
base_tag();
virtual ~base_tag();
virtual std::string get();
bool attribute_exists(std::string);
std::string get_attribute_value(std::string name);
bool add_attribute(std::string name, std::string value);
protected:
std::vector<std::pair<std::string, std::string>> _attributes;
};
// ------------------------------------------------------------
class xml_declaration : public base_tag
{
public:
xml_declaration();
~xml_declaration();
std::string get();
};
// ----------------------------------------------------------
class tag : base_tag
{
public:
tag(std::string name);
tag(std::string name, tag_type tt );
std::string get();
void set_ident(size_t ident);
protected:
std::string _name;
tag_type _tt;
};
#endif // _TAG_HPP_
File tag.cpp
#include "tag.hpp"
base_tag::base_tag()
{
}
base_tag::~base_tag()
{
_attributes.clear();
}
bool base_tag::attribute_exists(std::string name)
{
bool res = false;
for(auto & i : _attributes)
{
if (i.first == name)
res = true;
}
return res;
}
bool base_tag::add_attribute(std::string name, std::string value)
{
bool res = false;
if(!attribute_exists(name))
{
attribute a = std::make_pair(name, value);
_attributes.push_back(a);
res = true;
}
return res;
}
std::string base_tag::get()
{
return u8"<invalid_tag/>";
}
// -------------------------------------------------------
xml_declaration::xml_declaration(): base_tag()
{
add_attribute("version", "1.0");
add_attribute("encoding", "UTF-8");
add_attribute("standalone", "yes");
}
xml_declaration::~xml_declaration()
{ }
std::string xml_declaration::get()
{
std::string res = u8"<?xml";
for(auto & i : _attributes)
{
res.push_back(' ');
res += i.first;
res += u8"=\"";
res += i.second;
res += u8"\"";
}
res += u8" ?>";
return res;
}
// -------------------------------------------------------------
tag::tag(std::string name):base_tag(), _name(name)
{
_tt = tag_type::autoclosed;
}
tag::tag(std::string name, tag_type tt ): base_tag(), _name(name),
_tt(tt)
{ }
std::string tag::get()
{
std::string res = u8"";
bool with_attributes = !(_attributes.empty());
switch(_tt)
{
case tag_type::autoclosed : {
res = u8"<";
res += _name;
if(with_attributes)
{
for(auto & i : _attributes)
{
res.push_back(' ');
res += i.first;
res += u8"=\"";
res += i.second;
res += u8"\"";
}
};
res += u8"/>";
break;
}
case tag_type::opening : {
res = u8"<";
res += _name;
for(auto & i : _attributes)
{
res.push_back(' ');
res += i.first;
res += u8"=\"";
res += i.second;
res += u8"\"";
}
res += u8">";
break;
}
case tag_type::closing : {
res = u8"</";
res += _name;
res.push_back('>');
}
default : break;
}
return res;
}
File main.cpp
#include <iostream>
#include <map>
#include <utility>
#include "tag.hpp"
using namespace std;
enum class order
{
declaration = 0,
root = 1,
file_version = 4,
project = 10,
project_closing = 998,
root_closing = 1000
};
int main()
{
std::map<order, base_tag> tree;
xml_declaration decl;
cout << decl.get() << endl;
tag t1("project_file", tag_type::opening);
tag t2("project_file", tag_type::closing);
tree.insert(std::pair<order, base_tag>(order::declaration,
decl));
tree.insert(std::pair<order, base_tag>(order::root, t1));
tree.insert(std::pair<order, base_tag>(order::root, t2));
// tree.emplace(std::pair<order, base_tag>(order::root_closing, t1));
cout << tree.size() << endl;
return 0;
}
I'm using Code::Blocks with GCC 5.1.0 (on Windows 10).
As the error message says, tag inherits base_tag privately. If you change its headline into
class tag: public base_tag;
(same as your xml_declaration which inherits base_tag publicly) then it will officially be-a base tag.
A more serious problem is that you try to store instances of inherited classes in a container of a base class by value. What happens then is, objects get sliced and lose their whole derived functionality; you put an object of a derived class into a map, but you actually store an object of a based class in it (that's the reason some people tend to say polymorphic base classes should necessarily be abstract.) Use (smart) pointers or reference_wrappers as map's mapped type.
The common "solution" to use GetProcAddress with C++ is "extern "C", but that breaks overloading. Name mangling allows multiple functions to co-exist, as long as their signature differs. But is there a way to find these mangled names for GetProcAddress?
The VC++ compiler knows its own name mangling scheme, so why not use that? Inside template<typename T> T GetProcAddress(HMODULE h, const char* name), the macro __FUNCDNAME__ contains the mangled name of GetProcAddress. That includes the T part. So, inside GetProcAddress<void(*)(int), we have a substring with the mangled name of void(*)(int). From that, we can trivially derive the mangled name of void foo(int);
This code relies on the VC++ macro __FUNCDNAME__. For MinGW you'd need to base this on __PRETTY_FUNCTION__ instead.
FARPROC GetProcAddress_CppImpl(HMODULE h, const char* name, std::string const& Signature)
{
// The signature of T appears twice in the signature of T GetProcAddress<T>(HMODULE, const char*)
size_t len = Signature.find("##YA");
std::string templateParam = Signature.substr(0, len);
std::string returnType = Signature.substr(len+4);
returnType.resize(templateParam.size()); // Strip off our own arguments (HMODULE and const char*)
assert(templateParam == returnType);
// templateParam and returnType are _pointers_ to functions (P6), so adjust to function type (Y)
std::string funName = "?" + std::string(name) + "##Y" + templateParam.substr(2);
return ::GetProcAddress(h, funName.c_str());
}
template <typename T>
T GetProcAddress(HMODULE h, const char* name)
{
// Get our own signature. We use `const char* name` to keep it simple.
std::string Signature = __FUNCDNAME__ + 18; // Ignore prefix "??$GetProcAddress#"
return reinterpret_cast<T>(GetProcAddress_CppImpl(h, name, Signature));
}
// Showing the result
struct Dummy { };
__declspec(dllexport) void foo( const char* s)
{
std::cout << s;
}
__declspec(dllexport) void foo( int i, Dummy )
{
std::cout << "Overloaded foo(), got " << i << std::endl;
}
__declspec(dllexport) void foo( std::string const& s )
{
std::cout << "Overloaded foo(), got " << s << std::endl;
}
__declspec(dllexport) int foo( std::map<std::string, double> volatile& )
{
std::cout << "Overloaded foo(), complex type\n";
return 42;
}
int main()
{
HMODULE h = GetModuleHandleW(0);
foo("Hello, ");
auto pFoo1 = GetProcAddress<void (*)( const char*)>(h, "foo");
// This templated version of GetProcAddress is typesafe: You can't pass
// a float to pFoo1. That is a compile-time error.
pFoo1(" world\n");
auto pFoo2 = GetProcAddress<void (*)( int, Dummy )>(h, "foo");
pFoo2(42, Dummy()); // Again, typesafe.
auto pFoo3 = GetProcAddress<void (*)( std::string const& )>(h, "foo");
pFoo3("std::string overload\n");
auto pFoo4 = GetProcAddress<int (*)( std::map<std::string, double> volatile& )>(h, "foo");
// pFoo4 != NULL, this overload exists.
auto pFoo5 = GetProcAddress<void (*)( float )>(h, "foo");
// pFoo5==NULL - no such overload.
}
Use dumpbin /exports 'file.dll' to get the decorated / undecorated name of all the symbols.
It's impossible to do it just by using GetProcAddress. However, one way to do it would be to enumerate all the exported functions for that particular module, and do a pattern matching to find all the mangled names.
More specifically, refer to this answer here. The only change you will need to make would be to pass in TRUE for MappedAsImage parameter and the return value of GetModuleHandle for Base parameter to ImageDirectoryEntryToData function call.
void EnumerateExportedFunctions(HMODULE hModule, vector<string>& slListOfDllFunctions)
{
DWORD *dNameRVAs(0);
_IMAGE_EXPORT_DIRECTORY *ImageExportDirectory;
unsigned long cDirSize;
_LOADED_IMAGE LoadedImage;
string sName;
slListOfDllFunctions.clear();
ImageExportDirectory = (_IMAGE_EXPORT_DIRECTORY*)
ImageDirectoryEntryToData(hModule,
TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &cDirSize);
if (ImageExportDirectory != NULL)
{
dNameRVAs = (DWORD *)ImageRvaToVa(LoadedImage.FileHeader,
LoadedImage.MappedAddress,
ImageExportDirectory->AddressOfNames, NULL);
for(size_t i = 0; i < ImageExportDirectory->NumberOfNames; i++)
{
sName = (char *)ImageRvaToVa(LoadedImage.FileHeader,
LoadedImage.MappedAddress,
dNameRVAs[i], NULL);
slListOfDllFunctions.push_back(sName);
}
}
}
I can't quite fathom why you'd ever want/need a constexpr version of MSalters' solution, but here it is, complete with namespace mangling. Use as
using F = int(double*);
constexpr auto f = mangled::name<F>([]{ return "foo::bar::frobnicate"; });
constexpr const char* cstr = f.data();
where F is the function signature and foo::bar::frobnicate is the (possibly qualified) name of the function.
#include<string_view>
#include<array>
namespace mangled
{
namespace detail
{
template<typename F>
inline constexpr std::string_view suffix()
{
auto str = std::string_view(__FUNCDNAME__);
return str.substr(14, str.size() - 87);
}
template<typename L>
struct constexpr_string
{
constexpr constexpr_string(L) {}
static constexpr std::string_view data = L{}();
};
template<typename Name>
inline constexpr int qualifiers()
{
int i = -2, count = -1;
while(i != std::string_view::npos)
{
i = Name::data.find("::", i + 2);
count++;
}
return count;
}
template<typename Name>
inline constexpr auto split()
{
std::array<std::string_view, qualifiers<Name>() + 1> arr = {};
int prev = -2;
for(int i = arr.size() - 1; i > 0; i--)
{
int cur = Name::data.find("::", prev + 2);
arr[i] = Name::data.substr(prev + 2, cur - prev - 2);
prev = cur;
}
arr[0] = Name::data.substr(prev + 2);
return arr;
}
template<typename F, typename Name>
struct name_builder
{
static constexpr auto suf = detail::suffix<F>();
static constexpr auto toks = split<Name>();
static constexpr auto len = Name::data.size() + suf.size() - toks.size() + 6;
static constexpr auto str = [] {
std::array<char, len> arr = {};
arr[0] = '?';
int i = 1;
for(int t = 0; t < toks.size(); t++)
{
if(t > 0)
arr[i++] = '#';
for(auto c : toks[t])
arr[i++] = c;
}
arr[i++] = '#';
arr[i++] = '#';
arr[i++] = 'Y';
for(auto c : suf)
arr[i++] = c;
return arr;
}();
};
}
template<typename F, typename LambdaString>
inline constexpr std::string_view name(LambdaString)
{
using Cs = detail::constexpr_string<LambdaString>;
using N = detail::name_builder<F, Cs>;
return {N::str.data(), N::len};
}
}
GodBolt