Boost.Spirit parse string to number with radix - c++

I have a template function which casts string to number as following:
template <typename RetType,
typename Parser =
typename boost::spirit::traits::create_parser<RetType>::type>
inline std::enable_if_t<std::is_arithmetic<RetType>::value, RetType>
cast(const std::string &input)
{
RetType result;
if(input.empty())
{
// handle this
}
auto itBeg = input.cbegin();
auto itEnd = input.cend();
if(!bsq::parse(itBeg, itEnd, Parser(), result) || itBeg != itEnd)
{
// handle that
}
return result;
}
now I would like to create a function similar to above which will parse string that represents a number in some radix
template <typename RetType, unsigned Radix,
typename Parser =
typename boost::spirit::traits::create_parser<RetType>::type>
inline std::enable_if_t<std::is_arithmetic<RetType>::value, RetType>
cast(const std::string &input)
{
RetType result;
if(input.empty())
{
// handle this
}
auto itBeg = input.cbegin();
auto itEnd = input.cend();
if(!bsq::parse(itBeg, itEnd, Parser<RetType, Radix, 1 - 1>() /*something like this*/, result) || itBeg != itEnd)
{
// handle that
}
return result;
}
the Parser is invalid, of course, but what is the right way to define "automatic" arithmetic parser with radix?

I'd use qi::int_parser<> directly:
Live On Coliru
#include <boost/spirit/include/qi.hpp>
#include <type_traits>
template <typename RetType, unsigned Radix = 10, typename Parser = typename boost::spirit::qi::int_parser<RetType, Radix> >
inline typename std::enable_if<std::is_arithmetic<RetType>::value, RetType>::type
cast(const std::string &input)
{
RetType result;
if(input.empty())
{
// handle this
}
auto itBeg = input.cbegin();
auto itEnd = input.cend();
if(!boost::spirit::qi::parse(itBeg, itEnd, Parser(), result) || itBeg != itEnd)
{
// handle that
throw "oops";
}
return result;
}
int main() {
std::cout << cast<int> ("10") << "\n";
std::cout << cast<int, 2>("10") << "\n";
std::cout << cast<int, 8>("10") << "\n";
std::cout << cast<int, 16>("10") << "\n";
std::cout << cast<int, 16>("ee") << "\n";
}
Prints
10
2
8
16
238
Hint: to be very accurate you might want to detect signed/unsigned types and use uint_parser<> accordingly

Related

How can I make this custom fmt formatter code cleaner?

I want to write a custom formatter for simple structures inheriting from the existing formatters to access all their options. For example, I've written this for a rectangle:
#include <fmt/format.h>
template<typename T>
struct rect {
T x;
T y;
T width;
T height;
};
template <typename T>
struct fmt::formatter<rect<T>>: formatter<T> {
template <typename FormatContext>
auto format(rect<T> c, FormatContext& ctx) const {
auto out = ctx.out();
fmt::format_to(out, "[(");
out = formatter<T>::format(c.x, ctx);
fmt::format_to(out, ", ");
ctx.advance_to(out);
out= formatter<T>::format(c.y, ctx);
fmt::format_to(out, "), ");
ctx.advance_to(out);
out = formatter<T>::format(c.width, ctx);
fmt::format_to(out, " x ");
ctx.advance_to(out);
out = formatter<T>::format(c.height, ctx);
fmt::format_to(out, "]");
return out;
}
};
int main()
{
rect<double> f = {1.0, 2.3445, 3.14, 4};
fmt::print("{:.2f}\n", f);
rect<int> b = {1, 2, 3, 4};
fmt::print("{}\n", b);
return 0;
}
It works as expected, but the code inside the format() call seems a bit too convoluted. Q:
Is there a way of making it better, or at least cleaner?
Is format_to(...) the best option for adding those characters to the output?
Maybe you want like this. Note you have to implement parse and format methods both as per https://fmt.dev/latest/api.html#formatting-user-defined-types:
template < typename T >
struct fmt::formatter < rect < T >>: formatter < T > {
int floating_point_precision = 0;
constexpr auto parse(format_parse_context & ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(), end = ctx.end();
if (it != end && ( * it == '.')) it++;
if (it != end && ( * it >= '0' && * it <= '9')) {
floating_point_precision = * it - '0';
it++;
}
if (it != end && ( * it == 'f')) it++;
// Check if reached the end of the range:
if (it != end && * it != '}') throw format_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
template < typename FormatContext >
auto format(rect < T > c, FormatContext & ctx) const {
auto out = ctx.out();
if (floating_point_precision == 0)
fmt::format_to(out, "[({}, {}), {} x {}]", c.x, c.y, c.width, c.height);
else {
std::stringstream ss;
ss << "[({:." << floating_point_precision << "f}, {:." << floating_point_precision << "f}), "
"{:." << floating_point_precision << "f} x {:." << floating_point_precision << "f}]";
fmt::format_to(out, ss.str(), c.x, c.y, c.width, c.height);
}
return out;
}
};
You can make the code a bit cleaner as follows:
Move the element formatting logic into a separate function/lambda and reuse it in 4 places.
Remove redundant namespace qualification since the formatter is defined in the fmt namespace.
template <typename T>
struct fmt::formatter<rect<T>>: formatter<T> {
template <typename FormatContext>
auto format(rect<T> c, FormatContext& ctx) const {
auto out = ctx.out();
auto format_element = [&](const T& value) {
ctx.advance_to(out);
out = formatter<T>::format(value, ctx);
};
out = format_to(out, "[(");
format_element(c.x);
out = format_to(out, ", ");
format_element(c.y);
out = format_to(out, "), ");
format_element(c.width);
out = format_to(out, " x ");
format_element(c.height);
return format_to(out, "]");
}
};
Full example: https://godbolt.org/z/b8E88d3dG.
format_to is a reasonable way to output literal text but you could use std:copy instead.

Compiling a function with templates fails in variadic template function

I have come across a compiler error involving variadic templates. The following code is a strongly simplified version which reproduces the error in my original code:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
typedef std::vector<std::string> stringvec;
// dummy function: return string version of first vectorelemenr
template<typename T>
std::string vecDummy(const std::string sFormat, const T t) {
std::stringstream ss("");
if (t.size() > 0) {
ss << t[0];
}
return ss.str();
}
// recursion termination
std::string recursiveY(stringvec &vFlags, uint i) {
return "";
}
// walk through arguments
template<typename T, typename... Args>
std::string recursiveY(stringvec &vFlags, uint i, T value, Args... args) {
std::string sRes = "";
if (vFlags[i] == "%v") {
sRes += vecDummy(vFlags[i], value);
}
sRes += " "+recursiveY(vFlags, i+1, args...);
return sRes;
}
int main(void) {
stringvec vPasta = {"spagis", "nudle", "penne", "tortellini"};
stringvec vFormats = {"%v", "%s"};
std::string st = "";
st += recursiveY(vFormats, 0, vPasta, "test12");
std::cout << ">>" << st << "<<" << std::endl;
return 0;
}
This simple code should walk through the arguments passed to recursiveY() and, if the current format string is "%v" it would pass the corresponding argument to vecDummy() which would return a string version of the vector's first element (if there is one).
The error message from the compiler is
sptest2.cpp: In instantiation of ‘std::string vecDummy(std::string, T) [with T = const char*; std::string = std::__cxx11::basic_string<char>]’:
sptest2.cpp:30:25: required from ‘std::string recursiveY(stringvec&, uint, T, Args ...) [with T = const char*; Args = {}; std::string = std::__cxx11::basic_string<char>; stringvec = std::vector<std::__cxx11::basic_string<char> >; uint = unsigned int]’
sptest2.cpp:32:27: required from ‘std::string recursiveY(stringvec&, uint, T, Args ...) [with T = std::vector<std::__cxx11::basic_string<char> >; Args = {const char*}; std::string = std::__cxx11::basic_string<char>; stringvec = std::vector<std::__cxx11::basic_string<char> >; uint = unsigned int]’
sptest2.cpp:43:21: required from here
sptest2.cpp:12:11: error: request for member ‘size’ in ‘t’, which is of non-class type ‘const char* const’
12 | if (t.size() > 0) {
| ~~^~~~
It seems as if the compiler uses all types i pass to recursiveY() in main, but vecDummy() is designed to only work with vectors of some kind (and not with const char*, for example).
Is there a possibility to modify this code so that it will work as intended?
Is there perhaps a way of assuring the compiler that i will only pass vectors to vecDummy() (even at the risk of a runtime error or unexpected behaviour - similar to passing an integer to printf() when it expects a string)?
You can add an overload of vecDummy that handles the std::vector case and 'dumb down' the more general one to (say) just return an empty string:
// dummy function: return string version of first vectorelement (catch-all)
template<typename T>
std::string vecDummy(const std::string, const T) {
return "";
}
// dummy function: return string version of first vectorelement (vectors only)
template<typename T>
std::string vecDummy(const std::string, const std::vector <T> &t) {
std::stringstream ss("");
if (t.size() > 0) {
ss << t[0];
}
return ss.str();
}
Live demo
I think if constexpr can help here. I'm posting two solutions. (And a third, more complete one, at the end after an edit I did some time after posting)
First solution (only the function vecDummy changes). Here, vecDummy receives parameters of all types, doing its intended work only for vectors and doing nothing when the parameter is not a vector.
template<typename T>
std::string vecDummy(
[[maybe_unused]] const std::string sFormat,
[[maybe_unused]] const T t
)
noexcept
{
std::stringstream ss("");
if constexpr (std::is_same_v<T, stringvec>) {
if (t.size() > 0) {
ss << t[0];
}
}
else {
// nothing, since this is intended to work only for vectors
}
return ss.str();
}
Another solution is to move if constexpr inside recursiveY (and leave vecDummy unchanged). In this solution, vecDummy is only called for parameters of vector-type and when the format is "%v".
template<typename T, typename... Args>
std::string recursiveY(
const stringvec& vFormats,
std::size_t i,
T value, Args... args
)
noexcept
{
std::cout << "(1) i= " << i << '\n';
std::string res = "";
if (vFormats[i] == "%v") {
if constexpr (std::is_same_v<T, stringvec>) {
res += vecDummy(vFormats[i], value);
}
}
else {
if constexpr (not std::is_same_v<T, stringvec>) {
res += std::string(value);
}
}
res += " " + recursiveY(vFormats, i+1, args...);
return res;
}
EDIT
Following a question asked by OP in a comment, I've updated the solution to a more complete one in which std::vector<int> is allowed. Also, I've added the handling of the case "%s", which was lacking in the original post.
This update uses the construct is_vector that I found in this post.
The result of this code is >>spagis 1 1234 6789 test12 <<.
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
typedef std::vector<std::string> stringvec;
/* ------------------------------------------------ */
// copied from https://stackoverflow.com/a/12043020/12075306
template <typename T, typename _ = void>
struct is_vector {
static const bool value = false;
};
template <typename T>
struct is_vector< T,
typename std::enable_if<
std::is_same<T,
std::vector< typename T::value_type,
typename T::allocator_type >
>::value
>::type
>
{
static const bool value = true;
};
/* ------------------------------------------------ */
// dummy function: return string version of first vector element
template<typename T>
std::string vecDummy(
[[maybe_unused]] const std::string sFormat,
[[maybe_unused]] const std::vector<T>& t
)
noexcept
{
std::stringstream ss("");
if (t.size() > 0) { ss << t[0]; }
return ss.str();
}
// recursion termination
std::string recursiveY
([[maybe_unused]] const stringvec& vFlags, [[maybe_unused]] std::size_t i) noexcept
{ return ""; }
template<typename T, typename... Args>
std::string recursiveY(
const stringvec& vFormats,
std::size_t i,
T value, Args... args
)
noexcept
{
std::cout << "(1) i= " << i << '\n';
std::string res = "";
if (vFormats[i] == "%v") {
if constexpr (is_vector<T>::value) {
res += vecDummy(vFormats[i], value);
}
}
else {
if constexpr (not is_vector<T>::value) {
res += std::string(value);
}
}
res += " " + recursiveY(vFormats, i+1, args...);
return res;
}
int main(void) {
stringvec vPasta = {"spagis", "nudle", "penne", "tortellini"};
std::vector<int> vMoney1 = {1, 2, 3, 4};
std::vector<int> vMoney2 = {1234, 2, 3, 4};
std::vector<int> vMoney3 = {6789, 2, 3, 4};
stringvec vFormats = {"%v", "%v", "%v", "%v", "%s"};
std::string st = "";
st += recursiveY(vFormats, 0, vPasta, vMoney1, vMoney2, vMoney3, "test12");
std::cout << ">>" << st << "<<" << std::endl;
return 0;
}

How to look for an element inside a set of sets in C++?

Following is my code:
set<set<int,greater<int>>> output;
set<int,greater<int>> k;
k.insert(5);
output.insert(k);
Now how do I find out if element 5 is present in my 'output' set or not?
And how do I find out which set, inside my 'output' set, the element 5 belongs to?
Use a simple for loop:
size_t ii = 0;
for (const auto& inner : output) {
if (inner.count(5))
std::cout << "found in set " << ii << std::endl;
++ii;
}
There's a pre C++20 and a C++20 (using ranges) solution
Pre-C++20 I'm using std::find_if and set::find.
C++20 I'm using std::ranges::find_if and set::contains.
For the C++20 I'm also using std::optional std::reference_wrapper return type. And concepts, so it's a generic solution applicable to all sets and maps.
#include <set>
#include <algorithm>
template<typename T, typename Comp = std::less<T>>
std::set<T, Comp> const* findInSetOfSets(std::set<std::set<T, Comp>> const& setOfSets, T const& value) {
auto const it = std::find_if(cbegin(setOfSets), cend(setOfSets),
[&](std::set<T, Comp> const& set) { return set.find(value) != end(set); } );
if (it != cend(setOfSets)) return &*it;
return nullptr;
}
#include <optional>
#include <functional>
#include <type_traits>
template<typename T, typename U>
concept hasContains = requires(T& t, U& u) {
{ t.contains(u) } -> std::same_as<bool>;
};
template<std::ranges::input_range R, typename T,
typename U = std::remove_reference<R>::type::value_type>
requires hasContains<U, T>
[[nodiscard]] auto findCpp20(R&& r, T const& value) noexcept
->std::optional<std::reference_wrapper<U const>> {
auto it = std::ranges::find_if(r,
[&](U const& u) { return u.contains(value); } );
if (it != std::ranges::end(r)) return *it;
return std::nullopt;
}
#include <cstdio>
int main(){
std::set<std::set<int,std::greater<int>>> output;
std::set<int,std::greater<int>> k;
k.insert(5);
output.insert(k);
auto setPtr = findInSetOfSets(output, 5);
if (setPtr != nullptr) printf("set found!\n");
auto optionalSet = findCpp20(output, 5);
if (optionalSet.has_value()) printf("set found!\n");
}
godbolt
Here's a boolean function that looks for element p inside a set of sets s
bool findelem(int p, set<set<int>> const& s)
{
for (auto itr1= s.cbegin(); itr1 != s.cend(); ++itr1) {
for (auto itr2 = itr1->cbegin(); itr2 != itr1->cend(); ++itr2) {
if (*itr2==p) {
return true;
}
}
}
return false;
}

Too many if/else statements in converting user inputs to types in C++

I have a template class with 3 template arguments.
template <class T, class U, class Y>
class MyClass {};
I wanna get input from users by CLI arguments, something like ./cli float driver-x load
The first arg can be float or double
The second arg is a driver name: driver-x, driver-y, ...
The third argument is about the action type: load, unload, ...
If I want to create a new instance of MyClass based on user inputs, I have to define many if/else statements. Because a user inputs are string and I have to prepare a condition on them.
So, it will be something like this:
if (data_type == "float")
if (driver == "driver-x")
if (action == "load")
MyClass<float, DriverX, Load> t;
t......
As far as I know, it's impossible to store a type in a variable in C++.
So, is there any way exists to improve the if/else statements? Something like:
if (data_type == "float")
//
if (driver == "driver-x")
//
if (action == "load")
//
MyClass<......> t;
t.....;
Or any other way?
I'm looking for a way to improve these if/else statements.
Here's my take
template<typename T>
struct proxy { // or std::type_identity
using type = T;
};
template<typename... Ts>
using choice_of = std::variant<proxy<Ts>...>;
template<typename T, typename>
using type_const_t = T;
template<typename T, typename... Ts>
std::optional<choice_of<T, Ts...>> choose(std::string const &choice, std::string const &head, type_const_t<std::string const&, Ts>... tail) noexcept {
if(choice == head) return proxy<T>{};
else if constexpr(sizeof...(Ts) == 0) return std::nullopt;
else if(auto rec = choose<Ts...>(choice, tail...)) return std::visit(
[](auto rec) -> choice_of<T, Ts...> { return rec; },
*rec);
else return std::nullopt;
}
auto data_choice = choose<float, double>(data_type, "float", "double");
auto driver_choice = choose<DriverX, DriverY>(driver, "driver-x", "driver-y");
auto action_choice = choose<Load, Unload>(action, "load", "unload");
std::visit([](auto data_type_p, auto driver_p, auto action_p) {
auto t = MyClass<typename decltype(data_type_p)::type, typename decltype(driver_p)::type, typename decltype(action_p)::type>{};
// do stuff with t
}, data_choice.value(), driver_choice.value(), action_choice.value());
Complete example on Godbolt
You can build some machinery to do this for you, extracting it into a function call.
For example, here I build a tuple which contains strings and types, then I check a passed string against all of them:
#include <string_view>
#include <cstddef>
#include <tuple>
#include <utility>
#include <type_traits>
template<class T>
struct mapped_type {
const std::string_view key;
using type = T;
explicit constexpr operator bool() const noexcept {
return true;
}
};
namespace detail {
template<class K, class F, class M, std::size_t I>
constexpr void lookup_impl(const K& key, F&& f, M&& m, std::integral_constant<std::size_t, I>) {
using tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
if constexpr (I < std::tuple_size<tuple_t>::value) {
const auto& mapping = std::get<I>(m);
if (mapping.key == key) {
std::forward<F>(f)(mapping);
return;
}
lookup_impl(key, std::forward<F>(f), std::forward<M>(m), std::integral_constant<std::size_t, I + 1>{});
} else {
std::forward<F>(f)(std::false_type{});
}
}
}
// Calls `f` with the first value from `m` that matches the key
// or `std::false_type{}` if no key matches.
template<class K, class F, class M>
constexpr void lookup(const K& key, F&& f, M&& m) {
detail::lookup_impl(key, std::forward<F>(f), std::forward<M>(m), std::integral_constant<std::size_t, 0>{});
}
// This is our mapping for the first argument
inline constexpr auto data_type_map = std::make_tuple(
mapped_type<float>{ "float" },
mapped_type<double>{ "double" }
);
// Example usage
#include <iostream>
int main() {
const char* s = "float";
lookup(s, [](const auto& arg) {
if constexpr (!arg) {
std::cout << "Invalid type\n";
} else {
using type = typename std::remove_cv<typename std::remove_reference<decltype(arg)>::type>::type::type;
std::cout << "Got type: " << typeid(type).name() << '\n';
}
}, data_type_map);
}
And then you can call this recursively inside the lambda.
You could also create a version that takes a tuple of keys and a tuple of values to call one function with many arguments:
#include <string_view>
#include <tuple>
#include <utility>
#include <type_traits>
template<class T>
struct mapped_type {
const std::string_view key;
using type = T;
explicit constexpr operator bool() const noexcept {
return true;
}
};
namespace detail {
template<class K, class F, class M, std::size_t I>
constexpr void lookup_impl(F&& f, const K& key, M&& m, std::integral_constant<std::size_t, I>) {
using tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
if constexpr (I < std::tuple_size<tuple_t>::value) {
const auto& mapping = std::get<I>(m);
if (mapping.key == key) {
std::forward<F>(f)(mapping);
return;
}
lookup_impl(std::forward<F>(f), key, std::forward<M>(m), std::integral_constant<std::size_t, I + 1>{});
} else {
std::forward<F>(f)(std::false_type{});
}
}
template<class F, class K, class M, std::size_t I>
constexpr void multilookup_impl(F&& f, const K& keys, M&& mappings, std::integral_constant<std::size_t, I>) {
constexpr std::size_t size = std::tuple_size<typename std::remove_cv<typename std::remove_reference<K>::type>::type>::value;
if constexpr (I >= size) {
std::forward<F>(f)();
} else {
lookup_impl([&](const auto& current_lookup) {
multilookup_impl(
[&](const auto&... args) { std::forward<F>(f)(current_lookup, args...); },
keys, mappings, std::integral_constant<std::size_t, I + 1>{}
);
}, std::get<I>(keys), std::get<I>(mappings), std::integral_constant<std::size_t, 0>{});
}
}
}
template<class F, class K, class M>
constexpr void lookup(F&& f, const K& keys, M&& mappings) {
using map_tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
using key_tuple_t = typename std::remove_cv<typename std::remove_reference<K>::type>::type;
constexpr std::size_t size = std::tuple_size<key_tuple_t>::value;
static_assert(size == std::tuple_size<map_tuple_t>::value, "Wrong number of keys for given number of maps");
detail::multilookup_impl(std::forward<F>(f), keys, mappings, std::integral_constant<std::size_t, 0>{});
}
Which looks almost the same, but there's one more level of calls.
It would be used like this:
#include <iostream>
inline constexpr auto data_type_map = std::make_tuple(
mapped_type<float>{ "float" },
mapped_type<double>{ "double" }
);
inline constexpr auto driver_type_map = std::make_tuple(
mapped_type<DriverX>{ "driver-x" },
mapped_type<DriverY>{ "driver-y" }
);
inline constexpr auto action_type_map = std::make_tuple(
mapped_type<Load>{ "load" },
mapped_type<Unload>{ "unload" }
);
int main() {
const char* a = "float";
const char* b = "driver-x";
const char* c = "load";
lookup([](const auto& data, const auto& driver, const auto& action) {
if constexpr (!data) {
std::cout << "Could not parse data!\n";
} else if constexpr (!driver) {
std::cout << "Could not parse driver!\n";
} else if constexpr (!action) {
std::cout << "Could not parse action!\n";
} else {
using data_type = typename std::remove_cv<typename std::remove_reference<decltype(data)>::type>::type::type;
using driver_type = typename std::remove_cv<typename std::remove_reference<decltype(driver)>::type>::type::type;
using action_type = typename std::remove_cv<typename std::remove_reference<decltype(action)>::type>::type::type;
MyClass<data_type, driver_type, action_type> t;
std::cout << "Constructed a " << typeid(decltype(t)).name() << '\n';
}
},
std::array<const char*, 3>{ a, b, c },
std::forward_as_tuple(data_type_map, driver_type_map, action_type_map)
);
}
I think you are looking for something like X-macros:
#define YOUR_TABLE \
X(float, DriverX, "driver-x", Load) \
X(int, DriverY, "driver-y", action2) \
X(int, DriverY, "driver-y", action3)
#define X(data_type, driver, driverName, action) if((0 == strcmp(#data_type,argv[1])) \
&& (0 == strcmp(driverName,argv[2])) && (0 == strcmp(#action,argv[3])))\
{ \
MyClass<data_type, driver, action> t; \
t.... \
}
YOUR_TABLE
#undef X
Prepare your puke-bag, here is a far-from-elegant solution but
simple enough to be easily adapted.
The main drawback I see is that all the remaining of the application
that needs to work with the created instance must stand in a
lambda-closure (this solution does not return this instance).
Every possible argument is considered only once in a
dedicated function (not X times Y times Z if/else).
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
#include <string>
#include <stdexcept>
//----------------------------------------------------------------------------
struct DriverX { auto show() const { return "DriverX"; } };
struct DriverY { auto show() const { return "DriverY"; } };
struct Load { auto show() const { return "Load"; } };
struct Unload { auto show() const { return "UnLoad"; } };
template<typename RealType,
typename DriverType,
typename ActionType>
struct MyClass
{
RealType real{};
DriverType driver{};
ActionType action{};
auto show() const
{
return std::to_string(sizeof(real))+" bytes real, "+
driver.show()+", "+action.show();
}
};
//----------------------------------------------------------------------------
template<typename RealType,
typename DriverType,
typename DoEverythingFunction>
void
with_MyClass_3(const std::string &action,
DoEverythingFunction fnct)
{
if(action=="load")
{
return fnct(MyClass<RealType, DriverType, Load>{});
}
if(action=="unload")
{
return fnct(MyClass<RealType, DriverType, Unload>{});
}
throw std::runtime_error{"unexpected action: "+action};
}
template<typename RealType,
typename DoEverythingFunction>
void
with_MyClass_2(const std::string &driver,
const std::string &action,
DoEverythingFunction fnct)
{
if(driver=="driver-x")
{
return with_MyClass_3<RealType, DriverX>(action, fnct);
}
if(driver=="driver-y")
{
return with_MyClass_3<RealType, DriverY>(action, fnct);
}
throw std::runtime_error{"unexpected driver: "+driver};
}
template<typename DoEverythingFunction>
void
with_MyClass(const std::string &real,
const std::string &driver,
const std::string &action,
DoEverythingFunction fnct)
{
if(real=="float")
{
return with_MyClass_2<float>(driver, action, fnct);
}
if(real=="double")
{
return with_MyClass_2<double>(driver, action, fnct);
}
throw std::runtime_error{"unexpected real: "+real};
}
//----------------------------------------------------------------------------
int
main(int argc,
char **argv)
{
std::cout << "~~~~ hardcoded types ~~~~\n";
const MyClass<float, DriverX, Load> mc1;
std::cout << "mc1: " << mc1.show() << '\n';
const MyClass<double, DriverY, Unload> mc2;
std::cout << "mc2: " << mc2.show() << '\n';
std::cout << "\n~~~~ many types ~~~~\n";
for(const auto &real: {"float", "double", "int"})
{
for(const auto &driver: {"driver-x", "driver-y", "driver-z"})
{
for(const auto &action: {"load", "unload", "sleep"})
{
try
{
with_MyClass(real, driver, action,
[&](const auto &mc)
{
std::cout << "working with: " << mc.show() << '\n';
});
}
catch(const std::exception &e)
{
std::cerr << "!!! " << e.what() << " !!!\n";
}
}
}
}
if(argc>3)
{
std::cout << "\n~~~~ from command line ~~~~\n";
try
{
with_MyClass(argv[1], argv[2], argv[3],
[&](const auto &mc)
{
std::cout << "working with: " << mc.show() << '\n';
});
}
catch(const std::exception &e)
{
std::cerr << "!!! " << e.what() << " !!!\n";
}
}
return 0;
}

Access to specific index in ptree array

I am using boost library to manipulate a JSON file and I would like to access to a specific index of an array in this JSON.
boost::property_tree::ptree& jsonfile;
const boost::property_tree::ptree& array =
jsonfile.get_child("my_array");
What I would like to do is accessing to the value stored at index :
// This code does not compile
int value = array[index].get < int > ("property");
Just code it using the iterators:
template <typename T = std::string>
T element_at(ptree const& pt, std::string name, size_t n) {
return std::next(pt.get_child(name).find(""), n)->second.get_value<T>();
}
If you want to have the index checked for bounds:
template <typename T = std::string>
T element_at_checked(ptree const& pt, std::string name, size_t n) {
auto r = pt.get_child(name).equal_range("");
for (; r.first != r.second && n; --n) ++r.first;
if (n || r.first==r.second)
throw std::range_error("index out of bounds");
return r.first->second.get_value<T>();
}
Demo
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
template <typename T = std::string>
T element_at(ptree const& pt, std::string name, size_t n) {
return std::next(pt.get_child(name).find(""), n)->second.get_value<T>();
}
template <typename T = std::string>
T element_at_checked(ptree const& pt, std::string name, size_t n) {
auto r = pt.get_child(name).equal_range("");
for (; r.first != r.second && n; --n) ++r.first;
if (n || r.first==r.second)
throw std::range_error("index out of bounds");
return r.first->second.get_value<T>();
}
int main() {
ptree pt;
{
std::istringstream iss("{\"a\":[1, 2, 3, 4, 5, 6]}");
read_json(iss, pt);
}
write_json(std::cout, pt, false);
// get the 4th element:
std::cout << element_at_checked(pt, "a", 3) << "\n";
// get it as int
std::cout << element_at_checked<int>(pt, "a", 3) << "\n";
// get non-existent array:
try { std::cout << element_at_checked<int>(pt, "b", 0) << "\n"; } catch(std::exception const& e) { std::cout << e.what() << "\n"; }
try { std::cout << element_at_checked<int>(pt, "a", 6) << "\n"; } catch(std::exception const& e) { std::cout << e.what() << "\n"; }
}
Prints
{"a":["1","2","3","4","5","6"]}
4
4
No such node (b)
index out of bounds