Initialize a map of std::variant to represent a JSON file - c++

As part of a school project, I have to read a JSON config file to fill a custom Conf object which is defined as follow:
struct ConfValue;
using ConfObject = std::map<std::string, ConfValue>;
using ConfArray = std::vector<ConfValue>;
/**
* Represents a configuration value.
*/
struct ConfValue {
std::variant<std::monostate, ConfObject, ConfArray, std::string, long long, double, bool> v;
};
/**
* Configuration (format influenced by JSON).
*/
using Conf = ConfObject;
I would like to be able to construct a default object represented by this JSON file :
{
"port": 4242,
"module": "modheader",
"modulePath": ["../modules/", "./modules"]
}
Which can provide me a map of what to search on the real config file, the type of each field as well as a default value for each of them.
I attempted to do that using braced initialization, but no matter what I try, this won't compile:
Conf default_conf_{
{"port", ConfValue{4242}},
{"module", ConfValue{"modHeader"}},
{"modulePath", ConfArray{"../modules/", "./modules"}}
};
Is what I'm trying to do possible in c++ and if yes, how ? :)
Side question: Is it possible to get the type of a std::variant at run time ?

You might use:
Conf default_conf {
{"port", ConfValue{4242LL}},
{"module", ConfValue{"modHeader"}},
{"modulePath", ConfValue{ConfArray{ConfValue{"../modules/"}, ConfValue{"./modules"}}}}
};
Demo

There are two independent issues here.
First, 4242 isn't going to work without more help. You can reduce that to:
std::variant<long long, bool> v = 4242; // error
The conversion is ambiguous. So you have to make no or the other a better match. Hence, 4242LL.
Second, you just need more braces in the modulePath value - since we need to list-initialize all the ConfValues, we can't just construct them:
Conf default_conf_{{
{"port", {4242LL}},
{"module", {"modHeader"}},
{"modulePath", {ConfArray{{"../modules/"}, {"./modules"}}}}
}};
You need the ConfArray there basically to just identify what all these braces mean. If you added some constructors to ConfValue, you would be able to do away with that too.

Related

Array or object: how to use nlohmann::json for simple use cases?

I want to use the nlohmann JSON library in order to read options from a JSON file. Specifying options is optional, as reflected in the constructor in my code example. I'm assuming the JSON structure is an object in its root.
Unfortunately, I'm unable to use these options, because it is unclear to me how I can force the JSON structure to be an object. What is worse, merely initializing a member variable with a JSON object {} (magically?) turns it into an array [{}].
#include <cstdlib>
#include <iostream>
#include <nlohmann/json.hpp>
class Example {
public:
explicit Example(const nlohmann::json& options = nlohmann::json::object())
: m_options{options}
{
std::clog << options << '\n' << m_options << '\n';
}
private:
nlohmann::json m_options;
};
auto main() -> int
{
Example x;
Example y{nlohmann::json::object()};
return EXIT_SUCCESS;
}
This results in the following output. Notice that we have to perform some ceremony in order to use an empty object as the default value (= empty settings), with = nlohmann::json::object(). Also notice that the settings object changes its type as soon as we initialize the member value (!):
{}
[{}]
My use use case is quite straightforward, but I'm unable to extract settings, unless I explicitly check whether the settings are an array or an object.
Another thing that worries me is that incorrect code compiles without warning, e.g., code in which I use x.value("y") on a JSON array x containing an object with key "y". Only at run time do I discover that I should have done x.at(0).value("y") instead.
In brief, the whole situation is quite surprising to me. I must be missing something / I must be using this library in an unintended way?
nlohman is a very "modern" library, it uses a lot of features in C++. And that might make it harder to read and understand the code. But it is very flexible.
This short introduction might help
Introduction to nlohmann json
Parse text to json object is done like
constexpr std::string_view stringJson = R"({"k1": "v1"})";
nlohmann::json j = nlohmann::json::parse( stringJson.begin(), stringJson.end() );

How to pass a std::function or a function pointer into qtconnect?

This is mainly to clean up a bunch of code from my constructor. I have around 20+ lines of connect(object, func1, this, func2) in the constructor alone and I am trying to clean up the code by having a std::vector<std::tuple<QObject*,std::function<void()>,std::function<void>>>> connections;
It would work out quite nicely if I could do something like:
std::vector<std::tuple<QObject*,std::function<void()>,std::function<void>>>> connections = {
std::make_tuple(mySlider, std::bind(&QSlider::sliderReleased,mySlider, std::bind(&Foo::onSliderChanged,this)),
.
.
.
};
And then call it like this:
for(auto &&e : connections)
connect(std::get<0>(e),std::get<1>(e),this,std::get<2>(e));
However, when I do this I get an error that there is a substitution failure and a std::function<void()> cannot be converted into a function pointer. So decide to change it up and create actual function pointers like the following:
typename void(Foo::*fooFunc)();
typename void(QSlider::*sliderFunc)();
std::vector<std::tuple<QObject*,sliderFunc,fooFunc>> sliderConnections = {
std::make_tuple(mySlider, &QSlider::sliderReleased, &Foo::onSliderChanged),
.
.
.
};
And same thing, I then try to call it:
for(auto &&e : sliderConnections)
connect(std::get<0>(e),std::get<1>(e),this,std::get<2>(e));
However this also provides a similar error where there are no conversions. Which doesn't make any sense because now I am actually using a function pointer. Which according to the connection documentation it should be able to take a function pointer to connect them. So either I am passing it in incorrectly. Or what I am trying to achieve is not possible.
Any help would be appreciated!
After looking at G.M's comment I realized they were correct. A QObject* is not a QSlider* and therefore when trying to call the function QSlider::sliderReleased it couldn't connect the two because QObject does not have a slider. So once I changed that in the vector of tuples the code compiled just fine.
ex:
typedef void(Foo::*fooFunc)();
typedef void(QSlider::*sliderFunc)();
typedef void(QSpinBox::*spinFunc)();
const std::vector<std::tuple<QSlider*, sliderFunc, fooFunc>> sliderConnections = {
std::make_tuple(slider1, &QSlider::sliderReleased, &Foo::onSlider1Changed),
std::make_tuple(slider2, &QSlider::sliderReleased, &Foo::onSlider2Changed),
std::make_tuple(slider3, &QSlider::sliderReleased, &Foo::onSlider3Changed)
};
const std::vector<std::tuple<QSpinBox*, spinFunc, fooFunc>> spinConnections = {
std::make_tuple(spin1, &QSpinBox::editingFinished, &Foo::onSpin1Changed),
std::make_tuple(spin2, &QSpinBox::editingFinished, &Foo::onSpin2Changed),
std::make_tuple(spin3, &QSpinBox::editingFinished, &Foo::onSpin3Changed)
};
These will be private members in whatever class you are in charge of. And then in the constructor, instead of having 6 lines of connect(object,SIGNAL,object,SLOT), you can then put them into a function and call them like:
for(auto && tup : sliderConnections)
connect(std::get<0>(tup),std::get<1>(tup),this,std::get<2>(tup));
This successfully connects all the objects to their appropriate functions. Again, it's personal preference. I was just wondering if there was a way and G.M pointed me in the correct direction.

How can I add a global std::map to chaiscript?

I would like to pass a std::map to chaiscript. However, I'm not sure how to do this. My code is currently as follows:
#include <map>
#include <string>
#include <chaiscript/chaiscript.hpp>
int main(int argc, char* argv[]) {
chaiscript::ChaiScript chai;
auto data = std::map<std::string, std::string>{
{ "key1", "val1"},
{ "key2", "val2"},
};
chai.add_global(chaiscript::var(&data), "data");
chai.eval(R"(
print(data["key1"]);
)");
}
However, this code crashes with an exception saying, that chaiscript doesn't know what to do with the bracket [] operator. How can I fix this?
I could tell chaiscript what the right function is, but I would prefer it, if the map is compatible with chaiscripts internal Map type!
Update:
I found a bit in the documentation, which explains that the chaiscript map type supports arbitrary input. Looking at the code, this seems to be done by the Boxed_Value type. However, this probably means that it is fundamentally impossible to directly insert std::map into scripts.
I'm now thinking about either writing a custom type, or a conversion function to solve the problem. Keeping you posted...
As far as I can see, out-of-the-box-chaiscript only provides you with the std::map<std::string,chaiscript::Boxed_Value> map type. Therefore, if you want to add your own map to a script, you need to either provide chaiscript with a new type, or convert to the given one. Thus, I see the following solutions:
Case 1: You only need to get out a map from chaiscript to c++
This case can be found in the documentation. You need to supply a conversion function, and off you go.
chai.add(chaiscript::map_conversion<std::map<std::string, std::string>>());
auto map = chai.boxed_cast<std::map<std::string, std::string>>(chai.eval("data"));
Case 2: You only need to supply a map to chaiscript from c++
This is basically the same as Case 1, but you have to supply the conversion function yourself.
auto convert = [](const std::map<std::string, std::string>& std_map) {
auto chai_map = std::map<std::string, chaiscript::Boxed_Value>{};
for (auto&& entry : std_map)
chai_map.emplace(entry.first, chaiscript::var(entry.second));
return chai_map;
};
chai.add(chaiscript::var(convert(data)),"data");
Case 3: You want to share a global value between chaiscript and c++
This case is rather tricky. You either have to supply chaiscript with a get_map() and send_map() function, which handle the data synchronization:
chai.eval(R"(
data = get_map();
data["key1"] = "val1";
send_map(data);
)");
Or you have to add a custom data type, which handles the synchronization in the background.
My Solution:
Fortunately for my case, I don't really need a shared state between chaiscript and c++, and therefore can rely on the solution for Case 2.

boost::program_options : iterating over and printing all options

I have recently started to use boost::program_options and found it to be highly convenient. That said, there is one thing missing that I was unable to code myself in a good way:
I would like to iterate over all options that have been collected in a boost::program_options::variables_map to output them on the screen. This should become a convenience function, that I can simply call to list all options that were set without the need to update the function when I add new options or for each program.
I know that I can check and output individual options, but as said above, this should become a general solution that is oblivious to the actual options. I further know that I can iterate over the contents of variables_map since it is simply an extended std::map. I could then check for the type containd in the stored boost::any variable and use .as<> to convert it back to the appropriate type. But this would mean coding a long switch block with one case for each type. And this doesn't look like good coding style to me.
So the question is, is there a better way to iterate over these options and output them?
As #Rost previously mentioned, Visitor pattern is a good choice here. To use it with PO you need to use notifiers for your options in such a way that if option is passed notifier will fill an entry in your set of boost::variant values. The set should be stored separately. After that you could iterate over your set and automatically process actions (i.e. print) on them using boost::apply_visitor.
For visitors, inherit from boost::static_visitor<>
Actually, I made Visitor and generic approach use more broad.
I created a class MyOption that holds description, boost::variant for value and other options like implicit, default and so on. I fill a vector of objects of the type MyOption in the same way like PO do for their options (see boost::po::options_add()) via templates. In the moment of passing std::string() or double() for boosts::variant initialization you fill type of the value and other things like default, implicit.
After that I used Visitor pattern to fill boost::po::options_description container since boost::po needs its own structures to parse input command line. During the filling I set notifyer for each option - if it will be passed boost::po will automatically fill my original object of MyOption.
Next you need to execute po::parse and po::notify. After that you will be able to use already filled std::vector<MyOption*> via Visitor pattern since it holds boost::variant inside.
What is good about all of this - you have to write your option type only once in the code - when filling your std::vector<MyOption*>.
PS. if using this approach you will face a problem of setting notifyer for an option with no value, refer to this topic to get a solution: boost-program-options: notifier for options with no value
PS2. Example of code:
std::vector<MyOptionDef> options;
OptionsEasyAdd(options)
("opt1", double(), "description1")
("opt2", std::string(), "description2")
...
;
po::options_descripton boost_descriptions;
AddDescriptionAndNotifyerForBoostVisitor add_decr_visitor(boost_descriptions);
// here all notifiers will be set automatically for correct work with each options' value type
for_each(options.begin(), options.end(), boost::apply_visitor(add_descr_visitor));
It's a good case to use Visitor pattern. Unfortunately boost::any doesn't support Visitor pattern like boost::variant does. Nevertheless there are some 3rd party approaches.
Another possible idea is to use RTTI: create map of type_info of known types mapped to type handler functor.
Since you are going to just print them out anyway you can grab original string representation when you parse. (likely there are compiler errors in the code, I ripped it out of my codebase and un-typedefed bunch of things)
std::vector<std::string> GetArgumentList(const std::vector<boost::program_options::option>& raw)
{
std::vector<std::string> args;
BOOST_FOREACH(const boost::program_options::option& option, raw)
{
if(option.unregistered) continue; // Skipping unknown options
if(option.value.empty())
args.push_back("--" + option.string_key));
else
{
// this loses order of positional options
BOOST_FOREACH(const std::string& value, option.value)
{
args.push_back("--" + option.string_key));
args.push_back(value);
}
}
}
return args;
}
Usage:
boost::program_options::parsed_options parsed = boost::program_options::command_line_parser( ...
std::vector<std::string> arguments = GetArgumentList(parsed.options);
// print
I was dealing with just this type of problem today. This is an old question, but perhaps this will help people who are looking for an answer.
The method I came up with is to try a bunch of as<...>() and then ignore the exception. It's not terribly pretty, but I got it to work.
In the below code block, vm is a variables_map from boost program_options. vit is an iterator over vm, making it a pair of std::string and boost::program_options::variable_value, the latter being a boost::any. I can print the name of the variable with vit->first, but vit->second isn't so easy to output because it is a boost::any, ie the original type has been lost. Some should be cast as a std::string, some as a double, and so on.
So, to cout the value of the variable, I can use this:
std::cout << vit->first << "=";
try { std::cout << vit->second.as<double>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<int>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<std::string>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<bool>() << std::endl;
} catch(...) {/* do nothing */ }
I only have 4 types that I use to get information from the command-line/config file, if I added more types, I would have to add more lines. I'll admit that this is a bit ugly.

Syntax for std::binary_function usage

I'm a newbie at using the STL Algorithms and am currently stuck on a syntax error. My overall goal of this is to filter the source list like you would using Linq in c#. There may be other ways to do this in C++, but I need to understand how to use algorithms.
My user-defined function object to use as my function adapter is
struct is_Selected_Source : public std::binary_function<SOURCE_DATA *, SOURCE_TYPE, bool>
{
bool operator()(SOURCE_DATA * test, SOURCE_TYPE ref)const
{
if (ref == SOURCE_All)
return true;
return test->Value == ref;
}
};
And in my main program, I'm using as follows -
typedef std::list<SOURCE_DATA *> LIST;
LIST; *localList = new LIST;;
LIST* msg = GLOBAL_DATA->MessageList;
SOURCE_TYPE _filter_Msgs_Source = SOURCE_TYPE::SOURCE_All;
std::remove_copy(msg->begin(), msg->end(), localList->begin(),
std::bind1st(is_Selected_Source<SOURCE_DATA*, SOURCE_TYPE>(), _filter_Msgs_Source));
What I'm getting the following error in Rad Studio 2010. The error means "Your source file used a typedef symbol where a variable should appear in an expression. "
"E2108 Improper use of typedef 'is_Selected_Source'"
Edit -
After doing more experimentation in VS2010, which has better compiler diagnostics, I found the problem is that the definition of remove_copy only allows uniary functions. I change the function to uniary and got it to work.
(This is only relevant if you didn't accidentally omit some of your code from the question, and may not address the exact problem you're having)
You're using is_Selected_Source as a template even though you didn't define it as one. The last line in the 2nd code snippet should read std::bind1st(is_Selected_Source()...
Or perhaps you did want to use it as a template, in which case you need to add a template declaration to the struct.
template<typename SOURCE_DATA, typename SOURCE_TYPE>
struct is_Selected_Source : public std::binary_function<SOURCE_DATA *, SOURCE_TYPE, bool>
{
// ...
};
At a guess (though it's only a guess) the problem is that std::remove_copy expects a value, but you're supplying a predicate. To use a predicate, you want to use std::remove_copy_if (and then you'll want to heed #Cogwheel's answer).
I'd also note that:
LIST; *localList = new LIST;;
Looks wrong -- I'd guess you intended:
LIST *locallist = new LIST;
instead.