finding string format specifiers of variable argument list - c++

I have this Log function that call my logger (spdlog) methods :
template<typename... Args>
void Log(const char* fmt,
const Args&... args)
{
g_FileLogger->log(fmt, args...);
}
I want to change my logger (spdlog, represented by g_FileLoggre) to another logger. Unfortunately, "fmt" strings contain "{}" which are the the placeholder of variables "args". I want to change those "{}" by the correct format specifier (%s, %zu, %d etc...) as with the other loggers, I have to specify the correct format specifier.
Can you give me a quick and safe solution to generate a string that replaces "{}" by the correct format specifier of the variables.
Otherwise, spdlog is a great logging API, but since its API has been broken, we decided to choose another logger, for example in Centos the API is old whereas in Gentoo it is newer and code will not compile.

Create a format_converter function
template<typename... Args>
std::string format_converter(const char* fmt,
const Args&... args)
{...}
which parses the fmt string and converts it according to the arguments.
Then modify your function:
template<typename... Args>
void Log(const char* fmt,
const Args&... args)
{
auto new_fmt = format_converter(fmt, args...);
new_logger->log( new_fmt, args... );
}
Edit:
In the format_converter, template functions can be used to convert the types of parameters to strings. For example:
template< typename T >
const char* type_string( const T ); // primary template
const char* type_string( const char* ) // overload for c-string
{
return "%s";
}
template<>
const char* type_string< double >( const double ) // partial specialization for double
{
return "%d";
}
template<>
const char* type_string< int >( const int ) // partial specialization for int
{
return "%i";
}
// .....

Create the convert_format function that will replace {..} with what you specify. Create a std::map<std::string, std::string> object that contains the data for replacing.
#include <string>
#include <map>
std::string convert_format(const std::string& format, std::map<std::string, std::string> format_map) {
string ret;
for (int x = 0; x != format.size(); ++x) {
if (format[x] == '{') {
std::string key;
++x;
for (; x != format.size(); ++x) {
if (format[x] == '}') {
auto itr = format_map.find(key);
if (itr != format_map.end()) {
ret += (*itr).second;
}
break;
} else {
key.push_back(format[x]);
}
}
} else {
ret.push_back(format[x]);
}
}
return ret;
}
And now modifies the Log function as:
template<typename... Args>
void Log(const char* fmt,
const Args&... args)
{
// map that will contain the data.
std::map<std::string, std::string> format_map;
// IF you have an args named as number and a string
// replaces {number} with %d
format_map["number"] = "%d";
// for replacing '{string}' with %s
format_map["string"] = "%s"; // ... and so on
auto new_fmt = convert_format(std::string(fmt), format_map);
new_logger->log( new_fmt, args... );
}
EDIT: I think you need a macro that will pass name and value of the variable to the function Log.

Related

Create string according to the parameter pack arguments count

I have a function with a parameter pack, I pass this pack to the fmt::format function, and I want to create a formatStr according to the args count, meaning add "{}#" for each passed argument.
I can do it using iterating, but is it possible to do this with one line solution?
How to do it gracefully?
template <typename... Args>
void formatArgs( Args&&...args)
{
const auto size = sizeof...(Args);
std::string formatStr = ...// "{}#{}#{}..." - {}# should depend on args count
/*
std::ostringstream formatStr;
for (const auto& p : { args... })
formatStr << {}#";
*/
auto res = fmt::format(formatStr.c_str(), args...);
...
}
An ugly fold expression would work. It's only redeeming quality is that it's just one line, and in C++20 it should be a constexpr, so in C++20 this'll wind up to be a single std::string constructor call:
#include <string>
#include <iostream>
template<typename ...Args>
std::string string_from_args(Args && ...args)
{
return std::string{ ((args, std::string{"%s#"}) + ...) };
}
int main()
{
std::cout << string_from_args(1, 2, 3, 4) << "\n";
return 0;
}
If you want to create a string with all the arguments I suggest you use an std::ostringstream to create the string directly instead of going through std::format or similar.
How to create the string differs between version of C++. Before C++17 and pre-fold you can use overloading of functions to handle the argument packs:
// Handle single-argument string construction
template<typename Arg>
std::string string_from_args(Arg&& arg)
{
return (std::ostringstream() << arg).str();
}
// Handle multiple-argument string construction
template<typename First, typename ...Rest>
std::string string_from_args(First&& first, Rest&& ...rest)
{
return (std::ostringstream() << string_from_args(first) << '#' << string_from_args(rest...)).str();
}
Then with fold-expressions introduced in C++17:
template<typename ...Args>
std::string string_from_args(Args&& ...args)
{
char const* separator = "";
std::ostringstream os;
(((os << separator << args), separator = "#"), ...);
return os.str();
}
With a simple main function
int main()
{
std::cout << string_from_args(123, 456.789, "foo", 'b');
}
both variants should construct and print the string
123#456.789#foo#b
If all arguments have the same type which seems to be the case considering that your current solution involves iteration then you could use fmt::join. For example:
#include <fmt/ranges.h>
template <typename... Args>
std::string formatArgs(Args&&... args) {
return fmt::format("{}", fmt::join({args...}, ""));
}
int main() {
fmt::print("{}", formatArgs(1, 2, 3));
}
prints
123

Is it possible to union variable conversion function?

For example, there are three variable conversion functions.
//Int
int toInt(std::string input)
{
int ret = strtol(input.c_str(), 0, 10);
return ret;
}
//Double
double toDouble(std::string input)
{
double ret = strtod(input.c_str(), 0);
return ret;
}
//Const char*
const char* toChar(std::string input)
{
return input.c_str();
}
I want to combine these functions like below:
~~ toConvert(std::string input)
{
if ( Variable type is Int )
return strtol(~~~)
else if ( Varibale type is Double )
return strtod(~~~)
...
// Using
int i = toConvert<int>(input);
double d = toConvert<double>(input);
const char* c = toConvert<const char*>(input);
Is it possible?
Please help for implemet above function.
Your "using" code is passing a template argument to toConvert(), so make sure toConvert() is actually a template, and then you can specialize it for each type you want, eg:
template<typename T>
T toConvert(std::string &input) { return T{}; /* or, throw an exception... */ }
template<>
int toConvert<int>(std::string &input)
{
return strtol(input.c_str(), 0, 10);
}
template<>
double toConvert<double>(std::string &input)
{
return strtod(input.c_str(), 0);
}
template<>
const char* toConvert<const char*>(std::string &input)
{
return input.c_str();
}
Or, if you are using C++17 or later, you can instead use if constexpr in a single template:
#include <type_traits>
template<typename T>
T toConvert(std::string &input)
{
if constexpr (std::is_same_v<T, int>)
return strtol(input.c_str(), 0, 10);
else if constexpr (std::is_same_v<T, double>)
return strtod(input.c_str(), 0);
else if constexpr (std::is_same_v<T, const char*>)
return input.c_str();
else
return T{}; // or, throw an exception, or static_assert a compiler error...
}
Notice in either case that I changed the input parameter to std::string&. Your original code was taking in the std::string by value, which means it takes in a copy of the caller's string, and so the const char* conversion would return a dangling pointer to invalid memory when the copied std::string is freed upon return. By taking a reference instead, you avoid that copy.
You might be tempted to take in a const std::string& reference instead, but that would allow calls like toConvert<const char*>("string") to compile but still return a dangling pointer, since the compiler would have to create a temporary std::string to bind to the const reference. But a string literal can't bind to a non-const reference.
That's possible using C++17's if constexpr syntax. For example:
template <typename T>
constexpr bool dependent_false = false;
template <typename T>
T convertTo(const std::string& input)
{
if constexpr (std::is_same_v<T, int>) {
return std::stoi(input);
} else if constexpr (std::is_same_v<T, double>) {
return std::stod(input);
} else {
static_assert(dependent_false<T>, "Can't convert to the specified type");
}
}
Live Demo
The whole dependent_false thing exists to make the static_assertion dependent on the template parameter so that it only gets checked when the function template is instantiated. Just an odd quirk of the C++ template rules.
Note that I left the const char* case out. It has very different semantics from the others, since the pointer returned by c_str points to memory owned by the std::string object.

Template parameter pack peel args in pairs

I want to create a function for an ESP2866 microcontroller that saves an arbitrary number of configurations to a config file on the filesystem. I found a way to do it and I was wondering if it could be any better.
// Saves the configuration values to the file system
template <typename... Args>
void SaveConfig(const char *name, String &value, Args &...args)
{
Serial.println("Saving config...");
StaticJsonDocument<JSON_OBJECT_SIZE(2) + 200> doc;
SetData(doc, name, value, args...);
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile)
Serial.println("Failed to open config file for writing!");
serializeJson(doc, configFile);
serializeJson(doc, Serial);
configFile.close();
}
All i would need to do is:
doc[name] = value;
for each pair of arguments in the parameter pack. My solution was that i created a new function SetData() that calls itself with the parameter pack arguments, peeling off two parameters each iteration:
template <typename... Args>
static void SetData(JsonDocument &doc, const char *name, String &value, Args &...args)
{
doc[name] = value;
SetData(doc, args...);
}
But this creates another problem. When the parameter pack "runs out" it wants to call SetData() with no parameters. So now I have to create an overload of this function with no parameters (except doc).
So is there a better way of doing this?
If you really want to use templates instead of containers, you can try the following:
template<typename ...Args, std::size_t ...I>
void SetDataImpl(JsonDocument& doc, std::tuple<Args...> tup, std::index_sequence<I...>) {
int dummy[] = {
(doc[std::get<2*I>(tup)] = std::get<2*I+1>(tup), 0)...
};
}
template<typename ...Args>
void SetData(JsonDocument& doc, Args &...args) {
static_assert(sizeof...(args) % 2 == 0, "");
SetDataImpl(doc, std::forward_as_tuple(args...), std::make_index_sequence<sizeof...(args) / 2>{});
}
But as #HolyBlackCat mentioned, this way would be better.
void SetData(JsonDocument& doc, std::initializer_list<std::pair<const char*, String>> il = {}) {
for(const auto& elem : il) { // Just auto& maybe. Depends on json library implementation
doc[elem.first] = elem.second;
}
}

Dispatching from a runtime parameter to different overloads

Suppose I have a set of types :
constexpr std::tuple<int,double,string> my_types;
a set of values to identify them:
constexpr std::array<const char*,3> my_ids = {"int","double","string"}; // const char* instead of string to be constexpr-compatible
and an overload set
template<class T> bool my_fun(my_type complex_object) { /* some treatment depending on type T */ }
I have a manually dispatching function like that:
string my_disp_fun(my_type complex_object) {
const char* id = get_info(complex_object);
using namespace std::string_literals;
if (id == "int"s) {
return my_fun<int>(complex_object);
} else if (id == "double"s) {
return my_fun<double>(complex_object);
} else if (id == "string"s) {
return my_fun<string>(complex_object);
} else {
throw;
}
}
Because I see this pattern coming again and again with a different my_fun every time, I would like to replace it by something like that:
struct my_mapping {
static constexpr std::tuple<int,double,string> my_types;
static constexpr std::array<const char*,3> my_ids = {"int","double","string"}; // const char* instead of string to be constexpr-compatible
}
string my_disp_fun(my_type complex_object) {
const char* id = get_info(complex_object);
return
dispatch<my_mapping>(
id,
my_fun // pseudo-code since my_fun is a template
);
}
How to implement the dispatch function? I am pretty confident it can be done but so far, I can't think of a reasonably nice API that would still be implementable with template metaprograming tricks.
I am sure people already had the need for this kind of problem. Is there a name for this pattern? I don't really know how to even qualify it in succinct technical terms...
Side question: is it related to the pattern matching proposal? I'm not sure because the paper seems more interested in the matching part, not generating branchs from that, right ?
Leverage variant.
template<class T>struct tag_t{using type=T};
template<class T>constexpr tag_t<T> tag={};
template<class...Ts>
using tag_enum = std::variant<tag_t<Ts>...>;
now tag_enum is a type stores a type at runtime as a value. Its runtime representation is an integer (!), but C++ knows that integer represents a specific type.
We now just have to map your strings to integers
using supported_types=tag_enum<int, double, std::string>;
std::unordered_map<std::string, supported_types> name_type_map={
{"int", tag<int>},
{"double", tag<double>},
{"string", tag<std::string>},
};
this map can be built from an array and a tuple if you want, or made global somewhere, or made into a function.
The point is, a mapping of any kind to a tag_enum can be used to auto dispatch a function.
To see how:
string my_disp_fun(my_type complex_object) {
const char* id = get_info(complex_object);
return std::visit( [&](auto tag){
return my_fun<typename decltype(tag)::type>( complex_object );
}, name_type_map[id] };
}
refactoring this to handle whatever level of automation you want should be easy.
If you adopt the convention that you pass T as a tag_t as the first argument it gets even easier to refactor.
#define RETURNS(...)\
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
#define MAKE_CALLER_OF(...) \
[](auto&&...args) \
RETURNS( (__VA_ARGS__)(decltype(args)(args)...) )
now you can easily wrap a template function into an object
template<class F>
auto my_disp_fun(my_type complex_object F f) {
const char* id = get_info(complex_object);
return std::visit( [&](auto tag){
return f( tag, complex_object );
}, name_type_map[id] }; // todo: handle failure to find it
}
then
std::string s = my_disp_fun(obj, MAKE_CALLER_OF(my_fun));
does the dispatch for you.
(In theory we could pass the template parameter in the macro, but the above macros are generically useful, while one that did wierd tag unpacking are not).
Also we can make a global type map.
template<class T>
using type_entry = std::pair<std::string, tag_t<T>>;
#define TYPE_ENTRY_EX(NAME, X) type_entry<X>{ NAME, tag<X> }
#define TYPE_ENTRY(X) TYPE_ENTRY_EX(#X, X)
auto TypeTable = std::make_tuple(
TYPE_ENTRY(int),
TYPE_ENTRY(double),
TYPE_ENTRY_EX("string", std::string)
);
template<class Table>
struct get_supported_types_helper;
template<class...Ts>
struct get_supported_types_helper<std::tuple<type_entry<Ts>...>> {
using type = tag_enum<Ts...>;
};
template<class Table>
using get_supported_types = typename get_supported_types_helper<Table>::type;
From that you can do things like make the unordered map from the TypeTable tuple automatically.
All of this is just to avoid having to mention the supported types twice.
Since your functions have the same signature, you can use a std::map to map the ids to function pointers, eg:
template<class T>
std::string my_fun(my_type complex_object)
{
/* some treatment depending on type T */
return ...;
}
using my_func_type = std::string(*)(my_type);
const std::map<std::string, my_func_type> my_funcs = {
{"int", &my_fun<int>},
{"double", &my_fun<double>},
{"string", &my_fun<std::string>}
};
std::string my_disp_fun(my_type complex_object)
{
const char *id = get_info(complex_object);
auto iter = my_funcs.find(id);
if (iter == my_funcs.end())
throw ...;
return iter->second(complex_object);
}
Demo
I'm not sure that this is what you are looking for. But you can do it without the need to hold an additional array with the types:
// overload visitor trick
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// deduction guide
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
int main() {
std::tuple<int, double, const char*> tup = {10, 2.5, "hello"};
auto f = overloaded {
[](int arg){std::cout << arg << " + 3 = " << arg + 3 << std::endl;},
[](double arg){std::cout << arg << " * 2 = " << arg * 2 << std::endl;},
[](const std::string& arg){std::cout << "string: " << arg << std::endl;}
};
std::apply([&](const auto&... e){ (f(e), ...);}, tup);
}
Code: http://coliru.stacked-crooked.com/a/3bfdd35f89ceeff9

Template to match custom functors or stod, stoi, etc

I'm trying to store key-value parameters as string in a class named ModelConfig. Then I would like to automatically convert these values into specific types, either with custom conversion function or with standard functions stod, stof, stoi, and the like.
My class successfully parses parameters if I provide a custom conversion function, but I can't figure how to also accept standard functions. This is my approach:
class ModelConfig
{
public:
ModelConfig(void) = default;
void addParam(std::string pname, std::string pvalue) {
m_params[pname] = pvalue;
}
template <class F, typename... Args, class T = typename std::result_of<F&&(const std::string&, Args...)>::type>
T getParam(std::string pname, F&& pconv_functor) const
{
return pconv_functor(m_params.at(pname));
}
private:
std::map<std::string, std::string> m_params;
};
The class above, can be tested with:
#include <iostream>
#include <map>
#include <functional>
#include "ModelConfig.hpp"
int main(void)
{
ModelConfig mc;
mc.addParam("p1_float", "123.4");
mc.addParam("p2_double", "56.7");
mc.addParam("p3_bool", "true");
mc.addParam("p4_int", "-321");
auto functord = [](const std::string& s) {
return std::stod(s);
};
std::cout << mc.getParam("p2_double", functord) << "\n"; // OK.
std::cout << mc.getParam("p2_double", std::stod) << "\n"; // Error.
return 0;
}
How can I modify getParam to accept functions where their first argument is a string but which can have others with default values?
std::stod is overloaded, thus the compiler can't deduce which function to use.
You can use macro to write a generic wrapper:
#define wrapper(f) \
( [] (auto&&... args) -> decltype(auto) { \
return f(std::forward<decltype(args)>(args)...); \
} )
Then call it by:
std::cout << mc.getParam("p2_double", wrapper(std::stod)) << "\n";
An alternative and, IMO, better design is to store values as std/boost::variant<bool, long, double, std::string> and convert it to/from string during I/O. This also detects config file errors early on load, rather than on first value access which could happen much later and crash your application in front of the user.
Requiring the user of this API to always pass a conversion function is cumbersome. You can use boost::lexical_cast for converting strings to T:
#include <string>
#include <iostream>
#include <unordered_map>
#include <boost/lexical_cast.hpp>
struct ConvertProxy {
std::string const* value_;
template<class T>
T as() const {
return boost::lexical_cast<T>(*value_);
}
template<class T>
operator T() const {
return this->as<T>();
}
};
class ModelConfig {
std::unordered_map<std::string, std::string> m_params;
public:
void addParam(std::string pname, std::string pvalue) {
m_params[pname] = pvalue;
}
ConvertProxy getParam(std::string pname) const {
return {&m_params.at(pname)};
}
};
int main() {
ModelConfig mc;
mc.addParam("p1_float", "123.4");
mc.addParam("p2_double", "56.7");
mc.addParam("p3_bool", "true");
mc.addParam("p4_int", "-321");
// Example syntax.
double d1 = mc.getParam("p2_double");
auto d2 = mc.getParam("p2_double").as<double>();
auto d3 = static_cast<double>(mc.getParam("p2_double"));
std::cout << mc.getParam("p2_double").as<double>() << "\n";
std::cout << static_cast<double>(mc.getParam("p2_double")) << "\n";
}
The interface of boost::lexical_cast enables an easy solution here. If you cannot use boost::lexical_cast you should probably code up your own with a similar interface.
You can do this with no third party lib and without using preprocessor directives if you need:
by explicitely casting your standard functions pointers. Standard functions are overloaded for string and wstring so the compiler needs our help to determine which one to apply
and by slightly changing your functor's signature to adapt it to the signature of these standard functions as they have a second parameter.
These changes would be slight actually:
In ModelConfig:
class ModelConfig
{
[...]
// Adapted the functor's signature to comply to standard functions' signatures:
template <class F, typename... Args, class T = typename std::result_of<F && (const std::string&, size_t *)>::type>
T getParam(std::string pname, F&& pconv_functor) const
{
return pconv_functor(m_params.at(pname), 0);
}
[...]
};
In main():
int main(void)
{
[...]
// Adapted the functor to standard functions' signature
auto functord = [](const std::string& s, size_t * pos) {
return std::stod(s, pos);
};
// Unchanged, no need
std::cout << mc.getParam("p2_double", functord) << "\n"; // Still OK.
// Cast to determine which overload to use. The typedef helps having things readable.
typedef double(*StandardFunctionSignature)(const std::string&, size_t*);
std::cout << mc.getParam("p2_double", static_cast<StandardFunctionSignature>(std::stod)) << "\n"; // NO Error, it works now.
[...]
}
If you know the signature of the passed in overload set, you can make an additional overload that captures a specific function pointer from that set.
template <class F>
auto getParam(std::string pname, F&& pconv_functor) const
{
return pconv_functor(m_params.at(pname));
}
template <class F>
auto getParam(std::string pname, F(*pconv_functor)(const std::string&, std::size_t*)) const
{
return pconv_functor(m_params.at(pname), 0);
}
This has some obvious limitations, but can be useful in certain situations.