Read/Write inifiles with boost::{program_options,property_tree} - c++

Utilizing boost, I would like to
read options from an inifile, abort if an unknown option is encountered in the inifile and
save them later in another inifile.
The first part can be done with boost::program_options:
try{
inifile_options.add_options()
("ops1.i0", po::value<int>(&p.nx)->default_value(1), "test integer")
;
po::variables_map vm;
po::store(po::parse_config_file(pthfnini, inifile_options), vm);
po::notify(vm);
}
catch(exception& e){
cerr << "error: " << e.what() << "\n";
errorflag=1;
}
To the best of my knowledge writing an inifile is not possible with boost::program_options, but boost::property_tree works:
pt::ptree iniPropTree;
pt::ini_parser::write_ini("./used0.ini",iniPropTree);
Now the question is how can I translate the data stored in the po::variables_map to pt::ptree?
Reading the boost documentation leaves me with the impression that this is not possible. Is the following the only viable way?
iniPropTree.put<int>("ops1.i0",vm["ops1.i0"].as<int>();
It introduces quite a bit of redundancy for my taste. However, reading data into a property tree from the beginning does not seem to support checking for undefined/misspelled options.
Alternatively,is it possible to iterate over the contents of variables_map and somehow infer the corresponding datatype of each element?
The full code is here:
/*
* g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
*
*/
// C++11 & Boost libraries
#include <boost/program_options.hpp> // po::options_description, po::variables_map, ...
#include <boost/property_tree/ptree.hpp> // pt::ptree
#include <boost/property_tree/ini_parser.hpp> // write_ini()
#include <iostream> // cout
#include <fstream> // ofstream, ifstream
// namespaces
namespace po = boost::program_options;
namespace pt = boost::property_tree;
using namespace std;
struct params{
std::string inipthfn;
int i0;
};
void read_inifile(params &p, po::variables_map &vm){
// initialize variables
int errorflag=0;
std::ifstream pthfnini("./testini.ini");
po::options_description inifile_options("Allowed inifile options");
try{
inifile_options.add_options()
("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
;
;
po::store(po::parse_config_file(pthfnini, inifile_options), vm);
po::notify(vm);
}
catch(exception& e){
cerr << "error: " << e.what() << "\n";
errorflag=1;
}
pthfnini.close();
if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
}
int main(){
params p;
po::variables_map vm;
pt::ptree iniPropTree;
read_inifile(p,vm); // get options from inifile
// ??? conversion from vm -> pt ???
pt::ini_parser::write_ini("./used0.ini",iniPropTree); // save options to used.ini
cout << p.i0 << endl;
return 0;
}
The contents of the inifile "testini.ini" are:
[ops1]
i0=2

There's a conceptual problem here.
Commandline parameters are inherently textual.
Values in the variables-map aren't. The types used are configured in the value-semantics (part of your options-description).
If all your options have the same type, you can "cheat" and hard-code a conversion:
pt::ptree to_ptree(po::variables_map const& vm) {
pt::ptree tree;
for (auto& v : vm) {
if (!v.second.empty() && !v.second.defaulted())
tree.put(v.first, v.second.as<int>());
}
return tree;
}
Which saves:
[ops1]
i0=1
If you need more flexibility, you'd need access to the option descriptions at the very least. This is not the intended use of the library, and you'll probably run into undocumented parts of the implementation soon.

However, reading data into a property tree from the beginning does not seem to support checking for undefined/misspelled options
Well. That's not entirely true. You can make your own parse function that adds the logic. Use Property Tree Translators if you want.
Here's an extended example showing three parameters of varying types to be validated:
enum class restricted { value1, value2 };
struct params {
int i0 = 1;
restricted r1 = restricted::value2;
std::string s2 = "some default";
};
We will want to have a parse function like this:
params read_inifile(std::string filename) {
params p;
pt::ptree tree;
std::ifstream file(filename);
read_ini(file, tree);
p.i0 = tree.get("ops1.i0", 1);
p.r1 = tree.get("ops1.r1", restricted::value2);
p.s2 = tree.get("ops1.s2", "some default");
return p;
}
Streamable Types
To translate and validate the enum you merely need to implement the streaming operators:
static inline std::istream& operator>>(std::istream& is, restricted& r) {
std::string v;
if (is >> std::ws >> v) {
if (boost::iequals("value1", v))
r = restricted::value1;
else if (boost::iequals("value2", v))
r = restricted::value2;
else
throw std::runtime_error("invalid restricted value");
}
return is;
}
static inline std::ostream& operator<<(std::ostream& os, restricted r) {
switch(r) {
case restricted::value1: return os << "value1";
case restricted::value2: return os << "value2";
default: return os << "invalid";
}
}
Custom Translators
Let's imagine that i0 need custom validation. In this example, let's REQUIRE it to be an odd number:
namespace translators {
template <typename T>
struct must_be_odd {
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const std::string& str) const {
if (str.empty()) return boost::none;
T v = boost::lexical_cast<T>(str);
if (v % 2 == 0)
throw std::runtime_error("value must be odd");
return boost::make_optional(v);
}
boost::optional<std::string> put_value(const T& i0) {
assert(i0 % 2); // assert that the value was odd
return boost::lexical_cast<std::string>(i0);
}
};
static const must_be_odd<int> i0;
}
Now we can simply supply the translator (here, acting more like a custom validator like Boost Program Options also has them):
p.i0 = tree.get("ops1.i0", 1, translators::i0);
See it Live On Coliru
Unsupported Options
This is a bit more work. You'll have to iterate the tree checking the resultant paths against a known set. Here's a stab at a reasonably generic implementation of that (which should work correctly with case sensitive trees of any (wide) string type):
template <typename Tree,
typename Path = typename Tree::path_type,
typename Key = typename Path::key_type,
typename Cmp = typename Tree::key_compare>
std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
if (tree.size()) {
std::size_t n = 0;
for (auto& node : tree) {
Path sub = prefix;
sub /= node.first;
n += unsupported(node.second, supported, sub);
}
return n;
} else {
if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
return 1;
}
return 0;
}
You can use it like this:
if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
throw std::runtime_error(std::to_string(n) + " unsupported options");
}
Full Demo
Live On Coliru
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <set>
enum class restricted { value1, value2 };
static inline std::istream& operator>>(std::istream& is, restricted& r) {
std::string v;
if (is >> std::ws >> v) {
if (boost::iequals("value1", v))
r = restricted::value1;
else if (boost::iequals("value2", v))
r = restricted::value2;
else
throw std::runtime_error("invalid restricted value");
}
return is;
}
static inline std::ostream& operator<<(std::ostream& os, restricted r) {
switch(r) {
case restricted::value1: return os << "value1";
case restricted::value2: return os << "value2";
default: return os << "invalid";
}
}
struct params {
int i0 = 1;
restricted r1 = restricted::value2;
std::string s2 = "some default";
};
#include <boost/property_tree/ini_parser.hpp>
#include <boost/lexical_cast.hpp>
#include <fstream>
namespace pt = boost::property_tree;
namespace translators {
template <typename T>
struct must_be_odd {
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const std::string& str) const {
if (str.empty()) return boost::none;
T v = boost::lexical_cast<T>(str);
if (v % 2 == 0)
throw std::runtime_error("value must be odd");
return boost::make_optional(v);
}
boost::optional<std::string> put_value(const T& i0) {
assert(i0 % 2); // assert that the value was odd
return boost::lexical_cast<std::string>(i0);
}
};
static const must_be_odd<int> i0;
}
template <typename Tree,
typename Path = typename Tree::path_type,
typename Key = typename Path::key_type,
typename Cmp = typename Tree::key_compare>
std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
if (tree.size()) {
std::size_t n = 0;
for (auto& node : tree) {
Path sub = prefix;
sub /= node.first;
n += unsupported(node.second, supported, sub);
}
return n;
} else {
if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
return 1;
}
return 0;
}
params read_inifile(std::string filename) {
params p;
try {
pt::ptree tree;
std::ifstream file(filename);
read_ini(file, tree);
p.i0 = tree.get("ops1.i0", 1, translators::i0);
p.r1 = tree.get("ops1.r1", restricted::value2);
p.s2 = tree.get("ops1.s2", "some default");
if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
throw std::runtime_error(std::to_string(n) + " unsupported options");
}
} catch (std::exception const& e) {
std::cerr << "error: " << e.what() << "\n";
throw std::runtime_error("read_inifile");
}
return p;
}
pt::ptree to_ptree(params const& p) {
pt::ptree tree;
tree.put("ops1.i0", p.i0, translators::i0);
tree.put("ops1.r1", p.r1);
tree.put("ops1.s2", p.s2);
return tree;
}
int main() {
params const p = read_inifile("./testini.ini"); // get options from filename
write_ini("./used0.ini", to_ptree(p)); // save options to used.ini
std::cout << p.i0 << std::endl;
}
For input like
[ops1]
i0=17
i99=oops
[oops1]
also=oops
Prints
error: 2 unsupported options
terminate called after throwing an instance of 'std::runtime_error'
what(): read_inifile
And changing 17 to 18 prints:
error: value must be odd
terminate called after throwing an instance of 'std::runtime_error'
what(): read_inifile
On valid input, used0.ini will be written as expected:
[ops1]
i0=1
r1=value2
s2=some default

After giving this problem some more time, I found a suitable compact solution:
The key is to write a function that translates entries from the variables_map to the propTree depending on their datatype (thx to sehe for putting me on the right track):
void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){
for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
}
}
The full working example writes the correct inifile containing all read info:
/*
* g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
*
*/
// C++11 & Boost libraries
#include <boost/program_options.hpp> // po::options_description, po::variables_map, ...
#include <boost/property_tree/ptree.hpp> // pt::ptree
#include <boost/property_tree/ini_parser.hpp> // write_ini()
#include <iostream> // cout
#include <fstream> // ofstream, ifstream
// namespaces
namespace po = boost::program_options;
namespace pt = boost::property_tree;
using namespace std;
struct params{
std::string s0;
int i0;
};
void read_inifile(params &p, po::variables_map &vm){
// initialize variables
int errorflag=0;
std::ifstream pthfnini("./testini.ini");
po::options_description inifile_options("Allowed inifile options");
try{
inifile_options.add_options()
("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
("ops1.s0", po::value<std::string>(&p.s0)->default_value("default"), "test string")
;
;
po::store(po::parse_config_file(pthfnini, inifile_options), vm);
po::notify(vm);
}
catch(exception& e){
cerr << "error: " << e.what() << "\n";
errorflag=1;
}
pthfnini.close();
if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
}
void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){
for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
}
}
int main(){
params p;
po::variables_map vm;
pt::ptree iniPropTree;
read_inifile(p,vm); // get options from inifile
translate_variables_map_to_ptree(vm,iniPropTree); // conversion from vm -> pt
pt::ini_parser::write_ini("./used0.ini",iniPropTree); // save options to used.ini
cout << p.i0 << endl;
cout << p.s0 << endl;
return 0;
}
Taking a variables_map vm from reading the commandline, it is also possible to update the values in the property tree (from reading the inifile) with:
string opsName = "ops1.i0"; if(vm.count(opsName)) p.i0 = vm[opsName].as<int>();

Related

Clang - Getting SubstTemplateTypeParm full template information

I am traversing a clang AST, but am having trouble getting the desired information when traversing the type information of a declaration of the AST that contains a clang::SubstTemplateTypeParmType.
Given the following minimal input code to clang tooling
#include <map>
template <typename K, typename V> using Map = std::map<K, V>;
using String = std::string;
using ParameterMap = Map<String, String>;
ParameterMap someFunc();
When recursing through ParameterMap's type, clang says that the first Map parameter arg, String, is a clang::SubstTemplateTypeParmType. If I try to recurse further to get more info about String, by either desugaring, or getting the replacement type (code below), the underlying type is of Type::Record, and is std::basic_string. This is unexpected for me, as I would expect the underlying type to be a template specialization, something like basic_string<const char*, std::char_traits<const char*>, std::allocator<const char*>>. Since the node is a record, I can get that the map contains a std::basic_string, but cannot get the template information of basic_string. How can I get the template specialization information of basic_string in such a case?
case Type::SubstTemplateTypeParm:
{
auto substTemplateType = qualType->getAs<SubstTemplateTypeParmType>();
walkType(substTemplateType->getReplacementType());
return;
}
I understand the requirement of posting a minimal runnable code example, which is below. However this requires a clang tooling dependency, for which there are no pre-builts, so this is not so easy to plug and run.
The paths are hard coded so need to be updated based on your local setup. Here is the compile_commands file, which also has 3 paths to update. The compiler path, and the file path at the end twice.
[
{"directory":"F:/git/minRepro/","command":"\"C:/Program Files (x86)/compilers/clang.exe\" -Wall -isystem -g -std=c++14 -Wno-format -Wno-unneeded-internal-declaration -Werror F:/git/minRepro/exampleSource.cpp","file":"F:/git/minRepro/exampleSource.cpp"}
]
Code:
#pragma comment(lib,"Version.lib")
#include <clang/Tooling/JSONCompilationDatabase.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Frontend/FrontendAction.h>
#include <clang/Sema/SemaConsumer.h>
#include <clang/AST/Type.h>
#include <clang/AST/TemplateName.h>
#include <clang/AST/Decl.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/Frontend/CompilerInstance.h>
#include <iostream>
#include <cstdlib>
#include <cassert>
#include <vector>
#include <string>
class AstWalker : public clang::SemaConsumer
{
public:
AstWalker(clang::ASTContext& context)
: m_context(context)
{}
virtual void HandleTranslationUnit(clang::ASTContext& context)
{
using namespace clang;
for (auto declaration : context.getTranslationUnitDecl()->decls())
{
const auto&sm = m_context.getSourceManager();
if (!declaration->getBeginLoc().isValid())
continue;
// Only walk declarations from our file.
if (!sm.isInMainFile(sm.getSpellingLoc(declaration->getBeginLoc())))
continue;
// Find functions, and inspect their return type.
auto nodeKind = declaration->getKind();
if (nodeKind == Decl::Function)
{
auto funcDecl = cast<FunctionDecl>(declaration);
// Check for and ignore built-in functions.
if (funcDecl->getBuiltinID() != 0)
break;
walkType(funcDecl->getReturnType());
break;
}
}
}
void walkType(const clang::QualType& qualType)
{
using namespace clang;
auto classType = qualType->getTypeClass();
switch (classType)
{
case Type::Typedef:
{
auto typedefType = qualType->getAs<TypedefType>();
walkType(typedefType->desugar());
return;
}
case Type::TemplateSpecialization:
{
auto templateSpecialization = qualType->getAs<TemplateSpecializationType>();
if (templateSpecialization->isTypeAlias())
{
walkType(templateSpecialization->getAliasedType());
return;
}
std::string templateType = templateSpecialization->getTemplateName().getAsTemplateDecl()->getQualifiedNameAsString();
std::cout << templateType << "<";
auto numArgs = templateSpecialization->getNumArgs();
for (unsigned int i = 0; i < numArgs; ++i)
{
if (i > 0)
std::cout << ", ";
const clang::TemplateArgument& templateArg = templateSpecialization->getArg(i);
if (templateArg.getKind() == clang::TemplateArgument::ArgKind::Type)
{
walkType(templateArg.getAsType());
}
}
std::cout << ">";
return;
}
case Type::Record:
{
const auto record = qualType->getAs<RecordType>();
std::string recordQualifiedName = record->getAsRecordDecl()->getQualifiedNameAsString();
std::cout << recordQualifiedName;
return;
}
case Type::Elaborated:
{
auto elaboratedType = qualType->getAs<ElaboratedType>();
walkType(elaboratedType->desugar());
return;
}
case Type::SubstTemplateTypeParm:
{
auto substTemplateType = qualType->getAs<SubstTemplateTypeParmType>();
walkType(substTemplateType->desugar());
//Also tried getReplacementType.
//walkType(substTemplateType->getReplacementType());
return;
}
}
}
private:
clang::ASTContext& m_context;
};
class ExampleAction : public clang::ASTFrontendAction
{
public:
ExampleAction() {}
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance& compiler, llvm::StringRef inFile)
{
return std::unique_ptr<clang::ASTConsumer>(new AstWalker(compiler.getASTContext()));
}
};
int main(int argc, char **argv)
{
// Create the compilation database.
std::string errorOut;
std::unique_ptr<clang::tooling::JSONCompilationDatabase> compilationDatabase = clang::tooling::JSONCompilationDatabase::loadFromFile("F:/git/minRepro/compile_commands.json", errorOut, clang::tooling::JSONCommandLineSyntax::AutoDetect);
if (compilationDatabase == nullptr || !errorOut.empty())
{
std::cout << "[Error] Failed to load compilation database. Error=" << errorOut.c_str() << std::endl;
return false;
}
std::vector<std::string> headerFiles;
headerFiles.push_back("F:/git/minRepro/exampleSource.cpp");
clang::tooling::ClangTool tool(*compilationDatabase, llvm::ArrayRef<std::string>(headerFiles));
auto toolResult = tool.run(clang::tooling::newFrontendActionFactory<ExampleAction>().get());
if (toolResult == 1)
{
std::cout << "[Error] Error occurred. Check log. Aborting.\n";
assert(false);
return false;
}
}
In some cases, the template information is nested within ClassTemplateSpecializationDecl for a given RecordType. You can read it by casting your RecordDecl into a ClassTemplateSpecializationDecl.
const auto record = qualType->getAs<clang::RecordType>();
auto classTemplateSpecialization = llvm::dyn_cast<clang::ClassTemplateSpecializationDecl>(record->getAsRecordDecl());
if (classTemplateSpecialization)
{
const auto& args = classTemplateSpecialization->getTemplateArgs();
for (unsigned int i = 0; i < args.size(); ++i)
{
const clang::TemplateArgument& templateArg = args[i];
// Read arg info as needed.
}
}
else
{
// Not a template specialization.
}

find an element in std::vector of std::any

I want to check whether an element exists in the vector or not. I know the below piece of code will check it.
#include <algorithm>
if ( std::find(vector.begin(), vector.end(), item) != vector.end() )
std::cout << "found";
else
std::cout << "not found";
But I have the vector of any type. i.e. std::vector<std::any>
I am pushing elements into vector like this.
std::vector<std::any> temp;
temp.emplace_back(std::string("A"));
temp.emplace_back(10);
temp.emplace_back(3.14f);
So I need to find whether string "A" present in the vector or not. Can std::find help here?
As of now I am using below piece of code to do this
bool isItemPresentInAnyVector(std::vector<std::any> items, std::any item)
{
for (const auto& it : items)
{
if (it.type() == typeid(std::string) && item.type() == typeid(std::string))
{
std::string strVecItem = std::any_cast<std::string>(it);
std::string strItem = std::any_cast<std::string>(item);
if (strVecItem.compare(strItem) == 0)
return true;
}
else if (it.type() == typeid(int) && item.type() == typeid(int))
{
int iVecItem = std::any_cast<int>(it);
int iItem = std::any_cast<int>(item);
if (iVecItem == iItem)
return true;
}
else if (it.type() == typeid(float) && item.type() == typeid(float))
{
float fVecItem = std::any_cast<float>(it);
float fItem = std::any_cast<float>(item);
if (fVecItem == fItem)
return true;
}
}
return false;
}
This should work good I guess:
#include <vector>
#include <string>
#include <any>
#include <algorithm>
#include <iostream>
int main(){
std::vector<std::any> temp;
temp.emplace_back(std::string("A"));
temp.emplace_back(10);
temp.emplace_back(3.14f);
int i = 10;//you can use any type for i variable and it should work fine
//std::string i = "A";
auto found = std::find_if(temp.begin(), temp.end(), [i](const auto &a){
return typeid(i) == a.type() && std::any_cast<decltype(i)>(a) == i;
} );
std::cout << std::any_cast<decltype(i)>(*found);
}
Or to make the code a bit more generic and reusable:
#include <vector>
#include <string>
#include <any>
#include <algorithm>
#include <iostream>
auto any_compare = [](const auto &i){
return [i] (const auto &val){
return typeid(i) == val.type() && std::any_cast<decltype(i)>(val) == i;
};
};
int main(){
std::vector<std::any> temp;
temp.emplace_back(std::string("A"));
temp.emplace_back(10);
temp.emplace_back(3.14f);
//int i = 10;
std::string i = "A";
auto found = std::find_if(temp.begin(), temp.end(), any_compare(i));
std::cout << std::any_cast<decltype(i)>(*found);
}
Live demo
Important note: this is guaranteed to work only within single translation unit due to stadard requirements on std::any type (for example same types don't need to have same type identifier in different translation units)
Using an any for this kind of purpose is not a good use of any. The best way to go is just to use a variant - since you have a closed set of types:
struct Equals {
template <typename T>
constexpr bool operator()(T const& a, T const& b) const { return a == b; }
template <typename T, typename U>
constexpr bool operator()(T const& a, U const& b) const { return false; }
};
using V = std::variant<int, float, std::string>
bool isItemPresentInAnyVector(std::vector<V> const& items, V const& item)
{
auto it = std::find_if(items.begin(), items.end(), [&](V const& elem){
return std::visit(Equals{}, elem, item);
});
return it != items.end();
}
Actually it's even better, because as Kilian points out, variant's operator== already works exactly like this:
using V = std::variant<int, float, std::string>
bool isItemPresentInAnyVector(std::vector<V> const& items, V const& item)
{
return std::find(items.begin(), items.end(), item) != items.end();
}
Unfortunately if you want to find an std::any instance in a vector of std::any instances the answer is no.
std::any does need some "magic" for example to be able to handle the creation of unknown object types but this machinery is private and must only supports object creation and not equality comparison.
It would be possible to implement what you are looking for using the same approach, but not with standard std::any that doesn't publish the needed details. The "manager" template needs to enumerate all possible operations and, for example, in g++ implementation they're "access", "get_type_info", "clone", "destroy", "xfer".
variant is completely different, because explicitly lists all the allowed types and therefore in any place it's used can access all the methods.
Comparison with typeId() should be avoided since it's dependent from translation unit.
A much safer approach can be used with any_cast of pointers:
template<typename T>
std::optional<T> find(const std::vector<std::any>& v)
{
for(auto&& e : v){
if(auto ptr = std::any_cast<T>(&e)){
return *ptr;
}
}
return std::nullopt;
}
Find first element with the given type, or nullopt if it's not found.
If we want to find all element with a specific instead:
template<typename T>
std::vector<T> findAll(const std::vector<std::any>& v)
{
std::vector<T> out;
for(auto&& e : v){
if(auto ptr = std::any_cast<T>(&e)){
out.push_back(*ptr);
}
}
return out;
}
Usage:
int main()
{
std::vector<std::any> temp;
temp.emplace_back(std::string("A"));
temp.emplace_back(10);
temp.emplace_back(3.14f);
temp.emplace_back(12);
temp.emplace_back(std::string("B"));
auto outInt = findAll<int>(temp);
std::cout << "out int: " << outInt.size() << std::endl;
for(auto&& out : outInt)
std::cout << out << std::endl;
auto outString = findAll<std::string>(temp);
std::cout << "out string: " << outString.size() << std::endl;
for(auto&& out : outString)
std::cout << out << std::endl;
auto singleInt = find<int>(temp);
if(singleInt)
std::cout << "first int " << *singleInt << std::endl;
auto singleBool = find<bool>(temp);
if(!singleBool)
std::cout << "ok: bool not found" << std::endl;
}
LIVE DEMO
If the types are int, float and string (or a limited set of types), you can use a combination of std::variant and std::get_if to achieve what you want to do in a simple manner:
std::get_if is to determine which of the types is stored in the std::variant.
A minimal example:
#include <iostream>
#include <vector>
#include <string>
#include <variant>
int main(){
std::vector<std::variant<int, float, std::string>> temp;
temp.emplace_back(std::string("A"));
temp.emplace_back(10);
temp.emplace_back(3.14f);
for (const auto& var: temp) {
if(std::get_if<std::string>(&var)) {
if(std::get<std::string>(var) == "A") std::cout << "found string\n";
}
if(std::get_if<int>(&var)) {
if(std::get<int>(var) == 10) std::cout << "found int\n";
}
if(std::get_if<float>(&var)) {
if(std::get<float>(var) == 3.14f) std::cout << "found float\n";
}
}
}
Live Demo

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

Using Boost.Spirit.Lex and stream iterators

I want use Boost.Spirit.Lex to lex a binary file; for this purpose I wrote the following program (here is an extract):
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/support_multi_pass.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <fstream>
#include <iterator>
#include <string>
namespace spirit = boost::spirit;
namespace lex = spirit::lex;
#define X 1
#define Y 2
#define Z 3
template<typename L>
class word_count_tokens : public lex::lexer<L>
{
public:
word_count_tokens () {
this->self.add
("[^ \t\n]+", X)
("\n", Y)
(".", Z);
}
};
class counter
{
public:
typedef bool result_type;
template<typename T>
bool operator () (const T &t, size_t &c, size_t &w, size_t &l) const {
switch (t.id ()) {
case X:
++w; c += t.value ().size ();
break;
case Y:
++l; ++c;
break;
case Z:
++c;
break;
}
return true;
}
};
int main (int argc, char **argv)
{
std::ifstream ifs (argv[1], std::ios::in | std::ios::binary);
auto first = spirit::make_default_multi_pass (std::istream_iterator<char> (ifs));
auto last = spirit::make_default_multi_pass (std::istream_iterator<char> ());
size_t w, c, l;
word_count_tokens<lex::lexertl::lexer<>> word_count_functor;
w = c = l = 0;
bool r = lex::tokenize (first, last, word_count_functor, boost::bind (counter (), _1, boost::ref (c), boost::ref (w), boost::ref (l)));
ifs.close ();
if (r) {
std::cout << l << ", " << w << ", " << c << std::endl;
}
return 0;
}
The build returns the following error:
lexer.hpp:390:46: error: non-const lvalue reference to type 'const char *' cannot bind to a value of unrelated type
Now, the error is due to definition of concrete lexer, lex::lexer<>; in fact its first parameter is defaulted to const char *. I obtain the same error also if I use spirit::istream_iterator or spirit::make_default_multi_pass (.....).
But if I specify the correct template parameters of lex::lexer<> I obtain a plethora of errors!
Solutions?
Update
I have putted all source file; it's the word_counter site's example.
Okay, since the question was changed, here's a new answer, addressing some points with the complete code sample.
Firstly, you need to use a custom token type. I.e.
word_count_tokens<lex::lexertl::lexer<lex::lexertl::token<boost::spirit::istream_iterator>>> word_count_functor;
// instead of:
// word_count_tokens<lex::lexertl::lexer<>> word_count_functor;
Obviously, it's customary to typedef lex::lexertl::token<boost::spirit::istream_iterator>
You need to use min_token_id instead of token IDs 1,2,3. Also, make it an enum for ease of maintenance:
enum token_ids {
X = lex::min_token_id + 1,
Y,
Z,
};
You can no longer just use .size() on the default token value() since the iterator range is not RandomAccessRange anymore. Instead, employ boost::distance() which is specialized for iterator_range:
++w; c += boost::distance(t.value()); // t.value ().size ();
Combining these fixes: Live On Coliru
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/bind.hpp>
#include <fstream>
namespace spirit = boost::spirit;
namespace lex = spirit::lex;
enum token_ids {
X = lex::min_token_id + 1,
Y,
Z,
};
template<typename L>
class word_count_tokens : public lex::lexer<L>
{
public:
word_count_tokens () {
this->self.add
("[^ \t\n]+", X)
("\n" , Y)
("." , Z);
}
};
struct counter
{
typedef bool result_type;
template<typename T>
bool operator () (const T &t, size_t &c, size_t &w, size_t &l) const {
switch (t.id ()) {
case X:
++w; c += boost::distance(t.value()); // t.value ().size ();
break;
case Y:
++l; ++c;
break;
case Z:
++c;
break;
}
return true;
}
};
int main (int argc, char **argv)
{
std::ifstream ifs (argv[1], std::ios::in | std::ios::binary);
ifs >> std::noskipws;
boost::spirit::istream_iterator first(ifs), last;
word_count_tokens<lex::lexertl::lexer<lex::lexertl::token<boost::spirit::istream_iterator>>> word_count_functor;
size_t w = 0, c = 0, l = 0;
bool r = lex::tokenize (first, last, word_count_functor,
boost::bind (counter (), _1, boost::ref (c), boost::ref (w), boost::ref (l)));
ifs.close ();
if (r) {
std::cout << l << ", " << w << ", " << c << std::endl;
}
}
When run on itself, prints
65, 183, 1665
I think the real problem is not shown. You don't show first or last and I have a feeling you might have temporaries there.
Here's a sample I came up with to verify, perhaps you can see what it is you're doing ---wrong--- differently :)
Live on Coliru (memory mapped an byte-vector, via const char*)
And this alternative (using spirit::istream_iterator)
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fstream>
#ifdef MEMORY_MAPPED
# include <boost/iostreams/device/mapped_file.hpp>
#endif
namespace /*anon*/
{
namespace qi =boost::spirit::qi;
namespace lex=boost::spirit::lex;
template <typename Lexer>
struct mylexer_t : lex::lexer<Lexer>
{
mylexer_t()
{
fileheader = "hello";
this->self = fileheader
| space [ lex::_pass = lex::pass_flags::pass_ignore ];
}
lex::token_def<lex::omit>
fileheader, space;
};
template <typename Iterator> struct my_grammar_t
: public qi::grammar<Iterator>
{
template <typename TokenDef>
my_grammar_t(TokenDef const& tok)
: my_grammar_t::base_type(header)
{
header = tok.fileheader;
BOOST_SPIRIT_DEBUG_NODE(header);
}
private:
qi::rule<Iterator> header;
};
}
namespace /* */ {
std::string safechar(char ch) {
switch (ch) {
case '\t': return "\\t"; break;
case '\0': return "\\0"; break;
case '\r': return "\\r"; break;
case '\n': return "\\n"; break;
}
return std::string(1, ch);
}
template <typename It>
std::string showtoken(const boost::iterator_range<It>& range)
{
std::ostringstream oss;
oss << '[';
std::transform(range.begin(), range.end(), std::ostream_iterator<std::string>(oss), safechar);
oss << ']';
return oss.str();
}
}
bool parsefile(const std::string& spec)
{
#ifdef MEMORY_MAPPED
typedef char const* It;
boost::iostreams::mapped_file mmap(spec.c_str(), boost::iostreams::mapped_file::readonly);
char const *first = mmap.const_data();
char const *last = first + mmap.size();
#else
typedef char const* It;
std::ifstream in(spec.c_str());
in.unsetf(std::ios::skipws);
std::string v(std::istreambuf_iterator<char>(in.rdbuf()), std::istreambuf_iterator<char>());
It first = &v[0];
It last = first+v.size();
#endif
typedef lex::lexertl::token<It /*, boost::mpl::vector<char, unsigned int, std::string> */> token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;
typedef mylexer_t<lexer_type>::iterator_type iterator_type;
try
{
static mylexer_t<lexer_type> mylexer;
static my_grammar_t<iterator_type> parser(mylexer);
auto iter = mylexer.begin(first, last);
auto end = mylexer.end();
bool r = qi::parse(iter, end, parser);
r = r && (iter == end);
if (!r)
std::cerr << spec << ": parsing failed at: \"" << std::string(first, last) << "\"\n";
return r;
}
catch (const qi::expectation_failure<iterator_type>& e)
{
std::cerr << "FIXME: expected " << e.what_ << ", got '";
for (auto it=e.first; it!=e.last; it++)
std::cerr << showtoken(it->value());
std::cerr << "'" << std::endl;
return false;
}
}
int main()
{
if (parsefile("input.bin"))
return 0;
return 1;
}
For the variant:
typedef boost::spirit::istream_iterator It;
std::ifstream in(spec.c_str());
in.unsetf(std::ios::skipws);
It first(in), last;

boost::program_options "polymorphic" argument

I would like to use boost::program_options to create an executable which can be called as follows:
./example --nmax=0,10 # nmax is chosen randomly between 0 and 10
./example --nmax=9 # nmax is set to 9
./example # nmax is set to the default value of 10
What is the best way to achieve this, in a type-safe way, with minimum code?
I would like to use boost::program_options to create an executable
which can be called as follows:
the program_options library is very flexible, this can easily be supported by writing your own class with stream insertion and extraction operators.
#include <iostream>
#include <limits>
#include <stdlib.h>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
class Max
{
public:
Max() :
_max( std::numeric_limits<int>::max() )
{
}
Max(
int max
) :
_max( max )
{
}
Max(
int low,
int high
)
{
int value = rand();
value %= (high - low);
value += low;
_max = value;
}
int value() const { return _max; }
private:
int _max;
};
std::ostream&
operator<<(
std::ostream& os,
const Max& foo
)
{
os << foo.value();
return os;
}
std::istream&
operator>>(
std::istream& is,
Max& foo
)
{
std::string line;
std::getline( is, line );
if ( !is ) return is;
const std::string::size_type comma = line.find_first_of( ',' );
try {
if ( comma != std::string::npos ) {
const int low = boost::lexical_cast<int>( line.substr(0, comma) );
const int high = boost::lexical_cast<int>( line.substr(comma + 1) );
foo = Max( low, high );
} else {
foo = Max( boost::lexical_cast<int>(line) );
}
} catch ( const boost::bad_lexical_cast& e ) {
std::cerr << "garbage when convering Max value '" << line << "'" << std::endl;
is.setstate( std::ios::failbit );
}
return is;
}
int
main( int argc, char** argv )
{
namespace po = boost::program_options;
Max nmax;
po::options_description options;
options.add_options()
("nmax", po::value(&nmax)->default_value(10), "random number range, or value" )
("help,h", po::bool_switch(), "help text")
;
po::variables_map vm;
try {
po::command_line_parser cmd_line( argc, argv );
cmd_line.options( options );
po::store( cmd_line.run(), vm );
po::notify( vm );
} catch ( const boost::program_options::error& e ) {
std::cerr << e.what() << std::endl;
exit( EXIT_FAILURE );
}
if ( vm["help"].as<bool>() ) {
std::cout << argv[0] << " [OPTIONS]" << std::endl;
std::cout << std::endl;
std::cout << "OPTIONS:" << std::endl;
std::cout << options << std::endl;
exit(EXIT_SUCCESS);
}
std::cout << "random value: " << nmax.value() << std::endl;
}
sample session
samm:stackoverflow samm$ ./a.out
random value: 10
samm:stackoverflow samm$ ./a.out --nmax 55
random value: 55
samm:stackoverflow samm$ ./a.out --nmax 10,25
random value: 17
samm:stackoverflow samm$
The library doesn't offer "polymorphic" argument types like you suggest. Each argument has exactly one type. If you want to make it have different values based on the syntax of the argument, you need to add that functionality yourself.
The easy way is to do as Kerrek's comment suggests and use a string, and then parse it afterward. It doesn't really take much code.
Another way is to use a custom validator. Make up a special type dedicated to this format of argument, and then write a validate function that converts string values into values of your custom type. Throw an exception if validation fails; the Program_Options library will treat it just like a validation failure of any of the built-in types. I wrote an example validator in response to another question.
The code you'll write for this is pretty much the same code you'd write to parse the string after parsing the command line; it's just a matter of whether you build it into the argument type, or just process it afterward.
I am posting this code here, hoping it will prove useful to somebody. It is the "templatized" version of Sam Miller's answer.
#ifndef RANDOMCONSTANT_HH
#define RANDOMCONSTANT_HH
#include <boost/random.hpp>
boost::random::mt19937 g_randomConstantPrng(static_cast<unsigned int>(std::time(NULL) + getpid()));
template<typename T>
class RandomConstant
{
public:
RandomConstant() { /* nothing */ }
RandomConstant(T value) : _value(value) { /* nothing */ }
RandomConstant(int low, int high)
{
boost::random::uniform_int_distribution<> dist(low, high);
_value = dist(g_randomConstantPrng);
}
RandomConstant(double low, double high)
{
boost::random::uniform_real_distribution<> dist(low, high);
_value = dist(g_randomConstantPrng);
}
T value() const { return _value; }
private:
T _value;
};
template<typename T>
std::ostream&
operator<<(std::ostream& os, const RandomConstant<T>& foo)
{
os << foo.value();
return os;
}
template<typename T>
std::istream&
operator>>(std::istream &is, RandomConstant<T> &foo)
{
std::string line;
std::getline(is, line);
if (!is) return is;
const std::string::size_type comma = line.find_first_of( ',' );
if (comma != std::string::npos)
{
const T low = boost::lexical_cast<T>( line.substr(0, comma) );
const T high = boost::lexical_cast<T>( line.substr(comma + 1) );
foo = RandomConstant<T>(low, high);
}
else
{
foo = RandomConstant<T>(boost::lexical_cast<T>(line));
}
return is;
}
#endif /* RANDOMCONSTANT_HH */
Used as follows:
namespace po = boost::program_options;
po::options_description desc;
desc.add_options()
("help", "show help")
("intValue", po::value<RandomConstant<int>>()->default_value(3), "description 1")
("doubleValue", po::value<RandomConstant<double>>()->default_value(1.5), "description 2")
;
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cerr << desc << std::endl;
return EXIT_FAILURE;
}
int intValue = vm["intValue"].as<RandomConstant<int>>().value();
double doubleValue = vm["doubleValue"].as<RandomConstant<double>>().value();
You can probably use multitoken
po::options_description desc("Allowed options");
desc.add_options()
("nmax", po::value< std::vector< float > >()->multitoken()->default_value(10), "description")
;
...
float value;
if (vm.count["nmax"] == 2)
value = random value ...
else
value = vm["nmax"].as< std::vector< float > >()[0];