So I have implemented a rather trivial logger but I would like to extend it so I can pass arguments of data to it, possibly with formatting and I can't seem to figure out how to best do it.
So far it is written like so:
// Standard Headers.
#include <ostream>
#include <variant>
#include <memory>
#include <utility>
#include <mutex>
#include <array>
#include <string_view>
#include <iostream>
namespace Logger {
// Various logging severity levels.
enum class Level {
Info
};
class Log {
public:
// Takes standard streams cout, cerr, etc.
explicit Log(std::ostream& p_stream) : m_log(&p_stream) {}
// Create logger using std::make_unique<std::ofstream>(...) so ownership is passed.
explicit Log(std::unique_ptr<std::ostream> p_stream) : m_log(std::move(p_stream)) {}
template <typename T>
inline void info(T&& p_message);
private:
template <typename T>
void log(T&& p_msg) const {
auto const t_lock = std::lock_guard(*m_lock);
std::visit([&](auto&& p_ptr) {
(*p_ptr) << p_msg;
}, m_log);
};
std::ostream& stream() const {
return std::visit([](auto&& ptr) -> std::ostream& {
return *ptr;
}, m_log);
}
template <typename T>
inline void add(Logger::Level p_level, T&& p_message);
std::variant<std::unique_ptr<std::ostream>, std::ostream*> m_log;
std::unique_ptr<std::mutex> m_lock = std::make_unique<std::mutex>();
std::array<std::string_view, 1> m_levels = { "Info" };
};
template <typename T>
void Log::add(Level p_level, T&& p_message) {
auto const f_lock = std::lock_guard(*m_lock);
stream() << m_levels[static_cast<size_t>(p_level)] << ": " << p_message << '\n';
}
template <typename T>
inline void Log::info(T&& p_message) {
add(Level::Info, p_message);
}
}
int main() {
auto logger = Logger::Log(std::cout);
logger.info("Hello, world!");
return 0;
}
What I would like to do is when I use .info() be able to specify any number of arguments which will be replaced when writing to the log, similar to this:
logger.info("Some error message with arg: {}", 1);
How would I go about doing this? What is the best approach?
I usually use std::any. This is a non optimized version (uses copies instead of references, removes item from vector etc) but the basic idea is to convert a compile-time parameter pack to a run time one with this line:
std::vector<std::any> a = {args ...};
You can use references with, say, std::vector<std::any> a = {std::ref(args)...};
The full function would be like that:
template<typename ... many>
void safe_printf2(const char *s, many ... args)
{
using namespace std;
vector<any> a = {args ...};
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
}
else {
if (a.empty())
throw logic_error("Fewer arguments provided to printf");
if (a[0].type() == typeid(string)) cout << any_cast<string>(a[0]);
if (a[0].type() == typeid(int)) cout << any_cast<int>(a[0]);
if (a[0].type() == typeid(double)) cout << any_cast<double>(a[0]);
a.erase(a.begin());
s++;
}
}
cout << *s++;
}
}
Example:
safe_printf2("Hello % how are you today? I have % eggs and your height is %","Jack"s, 32,5.7);
For some weird reasons, I do something similar this way (you will have to adapt it to your needs):
inline
std::ostream &
txt(std::ostream &output,
const char *format)
{
return output << format;
}
template<typename First,
typename ...Args>
inline
std::ostream &
txt(std::ostream &output,
const char *format,
First &&first,
Args &&...args)
{
while(*format)
{
if(*format=='%')
{
return txt(output << std::forward<First>(first),
++format, std::forward<Args>(args)...);
}
output << *format++;
}
return output;
}
The two previous functions use the marker % in a format string to inject the next argument (whatever it is, a value, a format specifier, a custom object...) in the stream.
Of course some adapters could ease the usage:
template<typename ...Args>
inline
std::string
txt(const char *format,
Args &&...args)
{
std::ostringstream output;
txt(output, format, std::forward<Args>(args)...);
return output.str();
}
For example:
std::ostream &
operator<<(std::ostream &output,
const MyStuff &ms)
{
return output << '[' << ms.member1 << '|' << ms.member2 << ']';
}
...
MyStuff my_stuff= ... ;
auto msg=txt("an integer %, a (formatted) real %% and something else %\n",
12, std::setprecision(12), 34.56, my_stuff);
Then it should be possible to adjust Log::add() (then Log::info() and anything related) to make it usable in a way similar to what was expected in the question
logger.info("Some error message with arg: %", 1);
Hope that helps.
Related
For academic reasons I am trying to build a Generic Wrapper.
This one owns one method to convert the Wrapper to string: to_string.
In order to be able to make the job when wrapped types are complex or custom, the class can take a lambda to "stringify" the value.
Because the lambda can be null, a check must be done to build the resulting string by the "natural way" or using the lambda.
But as soon as I wrap a complex type (eg: a struct), the pre-compiler (Xcode dialecte: GNU++17) reports an error.
Let's see the code:
#include <iostream>
#include <sstream>
#include <any>
using namespace std;
template <typename T>
class Wrapper {
using Stringer = string(*)(any);
Stringer stringer;
public:
T value;
Wrapper(T value): value(value), stringer(nullptr) {}
Wrapper(T value, Stringer stringer): value(value), stringer(stringer) {}
void setValue(T value) { this->value = value; }
string to_string() const {
stringstream buffer;
buffer << "(vtw)";
if (stringer == nullptr) {
buffer << value; // << Invalid operands to binary expression ('std::stringstream' (aka 'basic_stringstream<char>') and 'const Coord')
} else {
buffer << stringer(value);
}
return buffer.str();
}
};
int main(int argc, const char * argv[]) {
using Coord = struct {int x; int y; };
auto coord = new Wrapper((Coord){-105,245},
[](any value)->string{ Coord value_ = any_cast<Coord>(value); stringstream buffer; buffer << value_.x << "," << value_.y; return buffer.str(); }
);
cout << coord->to_string() << endl;
return 0;
}
Any ideas how to work around it?
buffer << value; must be valid, which it is not for your unnamed Coord. You could add a constexpr-if check to see if it supports streaming:
string to_string() const {
ostringstream buffer;
buffer << "(vtw)";
if (stringer == nullptr) {
if constexpr(can_ostream_v<T>) { // test here
buffer << value;
}
} else {
buffer << stringer(value);
}
return buffer.str();
}
The can_ostream_v type trait used above could look like this:
#include <type_traits>
#include <utility>
template<class T>
struct can_ostream {
static std::false_type test(...);
template<class U>
static auto test(U) -> decltype(std::declval<std::ostream&>() << std::declval<U>(),
std::true_type{});
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
template<class T>
inline constexpr bool can_ostream_v = can_ostream<T>::value;
Demo
I have a class that uses operator<< chains to output stuff to both console and a file at the same time. I need to flush it whenever a line is broken, which happens with redefined endl (replaced with \n). This code doesn't work and spits out a lot of errors (no conversion from T to const char*). What is wrong?
#pragma once
#include <iostream>
#include <fstream>
/*class declaration*/
template <typename T>
inline Logger & Logger::operator<<(const T &a)
{
if (debug::enabled)
{
std::cout << a;
file << a;
if (this->previousLineBroken)
{
std::cout << std::flush;
file << std::flush;
this->previousLineBroken = false;
}
if (std::is_same<T, const char*>::value) {
this->previousLineBroken = (a == debug::endl);
}
return *this;
}
}
Removing const from (const T &a) just makes things worse with more errors.
UPD: previousLineBroken is bool and debug::endl is const char* = "\n".
//debug.h
#pragma once
#define logger *logPtr
#include "Classes.h"
#include "logger.h"
namespace debug
{
static const char* endl = "\n";
static const bool enabled = true;
}
using debug::endl;
Your if statement is not a compile-time branch, therefore the compiler will try to compile
this->previousLineBroken = (a == debug::endl);
for any T, even if
std::is_same<T, const char*>::value
is false. This might be the cause of your errors. You should add a MCVE to your question.
In C++17, you can make the if a compile-time branch by using if constexpr:
if constexpr(std::is_same<T, const char*>::value) {
this->previousLineBroken = (a == debug::endl);
}
In C++11, you can use an additional helper function and overloading instead:
template <typename T>
void setPreviousLineBroken(std::true_type, T a)
{
this->previousLineBroken = (a == debug::endl);
}
template <typename T>
void setPreviousLineBroken(std::false_type, T) { /* do nothing */ }
Your code will the look like:
template <typename T>
inline Logger & Logger::operator<<(const T &a)
{
if (debug::enabled)
{
std::cout << a;
file << a;
if (this->previousLineBroken)
{
std::cout << std::flush;
file << std::flush;
this->previousLineBroken = false;
}
setPreviousLineBroken(std::is_same<T, const char*>{});
return *this;
}
}
Assume we are working with Clang strictly. No other compiler is being used. Also note that Clang supports CXX ABI.
We are using C++14.
Normally, we would get demangled class name like so:
#include <cxxabi.h>
class GoodClass {
public:
virtual const char *foo() const noexcept;
}
const char *
GoodClass::foo() const noexcept
{
// Naive implementation, not gonna' check any errors and stuff.
int32_t status = 0;
return abi::__cxa_demangle(typeid(*this).name(), 0, 0, &status);
}
This method will help us when we need class names of public subclasses of this class:
class SomeSubclassOfGoodClass : public GoodClass { }
SomeSubclassOfGoodClass object;
std::cout << object.foo(); // prints "SomeSubclassOfGoodClass"
However, in static methods, we could not use this since there is no instance. Therefore, it is impossible to serve an object to the typeid directive.
The examplary method serves well (with polymorphism), however it needs an instance to operate. This would involve problems about OO (such as constructors).
What would you do in a situation like this?
Thank you for your attention.
The use of demangle needs a little work. At the moment you have a memory leak.
Here's one way to solve that:
#include <cxxabi.h>
#include <memory>
#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <cassert>
#include <stdexcept>
struct demangled_string
{
using ptr_type = std::unique_ptr<char, void(*)(void*)>;
demangled_string(ptr_type&& ptr) noexcept;
const char* c_str() const;
operator std::string() const;
std::ostream& write(std::ostream& os) const;
private:
ptr_type _ptr;
};
inline std::ostream& operator<<(std::ostream& os, const demangled_string& str)
{
return str.write(os);
}
inline std::string operator+ (std::string l, const demangled_string& r) {
return l + r.c_str();
}
inline std::string operator+(const demangled_string& l, const std::string& r)
{
return std::string(l) + r;
}
demangled_string demangle(const char* name);
demangled_string demangle(const std::type_info& type);
demangled_string demangle(std::type_index type);
template<class T>
demangled_string demangle(T* p) {
return demangle(typeid(*p));
}
template<class T>
demangled_string demangle()
{
return demangle(typeid(T));
}
// implementation
demangled_string::demangled_string(ptr_type&& ptr) noexcept
: _ptr(std::move(ptr))
{}
std::ostream& demangled_string::write(std::ostream& os) const
{
if (_ptr) {
return os << _ptr.get();
}
else {
return os << "{nullptr}";
}
}
const char* demangled_string::c_str() const
{
if (!_ptr)
{
throw std::logic_error("demangled_string - zombie object");
}
else {
return _ptr.get();
}
}
demangled_string::operator std::string() const {
return std::string(c_str());
}
demangled_string demangle(const char* name)
{
using namespace std::string_literals;
int status = -4;
demangled_string::ptr_type ptr {
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
if (status == 0) return { std::move(ptr) };
switch(status)
{
case -1: throw std::bad_alloc();
case -2: {
std::string msg = "invalid mangled name~";
msg += name;
auto p = (char*)std::malloc(msg.length() + 1);
strcpy(p, msg.c_str());
return demangled_string::ptr_type { p, std::free };
}
case -3:
assert(!"invalid argument sent to __cxa_demangle");
throw std::logic_error("invalid argument sent to __cxa_demangle");
default:
assert(!"PANIC! unexpected return value");
throw std::logic_error("PANIC! unexpected return value");
}
}
demangled_string demangle(const std::type_info& type)
{
return demangle(type.name());
}
demangled_string demangle(std::type_index type)
{
return demangle(type.name());
}
std::string method(const demangled_string& cls, const char* method)
{
return std::string(cls) + "::" + method;
}
// test
class test_class
{
using this_class = test_class;
static auto classname() { return demangle<this_class>(); }
public:
static void test1() {
std::cout << method(demangle<this_class>(), __func__) << std::endl;
std::cout << method(classname(), __func__) << std::endl;
}
void test2() {
std::cout << method(demangle(this), __func__) << std::endl;
std::cout << method(classname(), __func__) << std::endl;
}
};
int main()
{
test_class t;
t.test1();
t.test2();
}
expected output:
test_class::test1
test_class::test1
test_class::test2
test_class::test2
The typeid operator may also be applied to a type, not just an expression: typeid(GoodClass) ought to work when you cannot access this.
Edit: without an instance you need to turn to static polymorphism. You could have a mix in base class Identifiable<X> which has a static method with the code you suggested above, but using typeid(X) instead. Your classes need to extend this class passing themselves as the template parameter (the curiously recursive template pattern), but it is not possible to ensure that a class does so:
class C : public Identifiable<C> {}; // method returns C
class D : public Identifiable<C> {}; // also returns C
I want to be able to call arbitrary functions with arguments packed in variant class that is not in my control (let's call it blackbox). I have written a function template unpack<T> which extracts value from blackbox that needs to be specialized for target types. This works fine for arguments passed by value. However, I have no idea how to handle pass-by reference:
#include <string>
#include <functional>
#include <iostream>
#include <utility>
#include <type_traits>
/* Variant container */
struct blackbox
{
int int_value() const { return 42; }
bool bool_value() const { return true; }
std::string string_value() const { return "str"; }
};
/* Unpack function templates */
template<typename T>
T unpack(const blackbox &v)
{
static_assert(sizeof(T) == 0, "This template has to be specialized");
}
template<>
int unpack(const blackbox &v)
{
return v.int_value();
}
template<>
bool unpack(const blackbox &v)
{
return v.bool_value();
}
template<>
std::string unpack(const blackbox &v)
{
return v.string_value();
}
/* Call function with arguments extracted from blackbox */
template<typename T>
void call(std::function<void(T)> f, const blackbox &v)
{
f(unpack<T>(v));
}
/* Sample functions */
void f_int(int i) { std::cout << "f_int(" << i << ")" << std::endl; }
void f_bool(bool b) { std::cout << "f_bool(" << b << ")" << std::endl; }
void f_str(std::string s) { std::cout << "f_str(" << s << ")" << std::endl; }
void f_str_ref(const std::string &s) { std::cout << "f_str_ref(" << s << ")" << std::endl; }
int main()
{
blackbox b;
// direct call
f_str_ref(b.string_value());
// indirect call
call(std::function<void(int)>(f_int), b);
call(std::function<void(bool)>(f_bool), b);
call(std::function<void(std::string)>(f_str), b);
call(std::function<void(const std::string&)>(f_str_ref), b); //doesn't work
return 0;
}
I need unpack specialization that forwards std::string instances to functions that take const std::string& argument. Defining
template<>
const std::string& unpack(const blackbox &v)
{
return v.string_value();
}
obviously doesn't work, because reference to local variable is returned. Not defining unpack specialization for const std::string& causes static assertion to fail.
Ideally, unpack<std::string> should be used for const std::string&, but providing separate specialization would be sufficient.
What you need is std::decay<T> from <type_traits>, that removes cv-qualifiers and references from given type (unless this is a function or array type):
static_assert(std::is_same<std::decay<const std::string&>::type,
std::string>::value, "!");
That being said, you can use the following syntax:
f(unpack<typename std::decay<T>::type>(v));
so that any variation of [cv/&/&&] std::string type will evaluate to plain std::string.
I am overloading operator << to implement a stream like interface for a class:
template<typename T>
CAudit& operator << ( const T& data ) {
audittext << data;
return *this;
}
CAudit& operator << ( LPCSTR data ) {
audittext << data;
return *this;
}
The template version fails to compile with "fatal error C1001: INTERNAL COMPILER ERROR (compiler file 'msc1.cpp', line 1794)". Non-template functions all compile correctly.
Is this due to VC6s deficiencies when handling templates and is there a way around this?
Thanks,
Patrick
EDIT :
the full class is:
class CAudit
{
public:
/* TODO_DEBUG : doesn't build!
template<typename T>
CAudit& operator << ( const T& data ) {
audittext << data;
return *this;
}*/
~CAudit() { write(); }//If anything available to audit write it here
CAudit& operator << ( LPCSTR data ) {
audittext << data;
return *this;
}
//overload the << operator to allow function ptrs on rhs, allows "audit << data << CAudit::write;"
CAudit& operator << (CAudit & (*func)(CAudit &))
{
return func(*this);
}
void write() {
}
//write() is a manipulator type func, "audit << data << CAudit::write;" will call this function
static CAudit& write(CAudit& audit) {
audit.write();
return audit;
}
private:
std::stringstream audittext;
};
The problem occurs with the function overload of operator << which is used to allow write() to be used as a stream manipulator:
CAudit audit
audit << "Billy" << write;
That overload of the template for function pointers surely is too much for good old Visual Studio 6.
As a workaround you could define a type for your manipulator and overload operator<< for that type.
Here's some code:
#include "stdafx.h"
#include <string>
#include <iostream>
#include <sstream>
#include <windows.h>
class CAudit {
std::ostringstream audittext;
void do_write() {}
public:
~CAudit() { do_write(); }
// types for manipulators
struct Twrite {};
// manipulators
static Twrite write;
// implementations of <<
template<typename T>
CAudit& operator << ( const T& data ) {
audittext << data;
return *this;
}
CAudit& operator << ( LPCSTR data ) {
audittext << data;
return *this;
}
CAudit& operator << ( Twrite& ) {
do_write();
return *this;
}
};
// static member initialization
CAudit::Twrite CAudit::write;
int main(int argc, char* argv[])
{
CAudit a;
int i = 123;
const char * s = "abc";
a << i << s << CAudit::write;
return 0;
}
The kind of error definitely looks like the kind of crashes caused by VC6's pre-previous-standard implementation of templates.
The best advice is of course to upgrade to either VC7.0, 7.1, 8.0, 9.0 or the beta of 10.
To compare that to Windows versions, it's still using Windows 98 when Me, 2000, XP, Vista and 7 are available.
Having said that, you can simplify the lookup a lot by a simple trick:
class CAudit {
template<typename T>
CAudit& operator<<(T const& t) {
this->print(t);
return *this;
}
private:
void print(int);
void print(LPCSTR);
void print(CAudit & (*func)(CAudit &));
template<typename T> print(T const&);
};
The hope here is that the first lookup of operator<< finds the single member template. The other operator<< candidates are non-members for other classes and built-ins. They should be unambiguously worse that this template. The second lookup inside your operator only needs to deal with CAudit members called print.
template<typename T>
CAudit& operator << (T data ) {
audittext << data;
return *this;
}
EDIT:
#include <iostream>
using namespace std;
class CAudit{
public:
CAudit(){}
template< typename T >
CAudit &operator<<(T arg);
CAudit &operator<<(char s);
};
template< typename T>
void oldLog(T arg){
cout << arg;
}
template< typename T >
CAudit &CAudit::operator<<(T arg){
oldLog( arg );
return *this;
}
CAudit &CAudit::operator<<(char arg){
oldLog( arg );
return *this;
}
int main(){
CAudit e;
e << "Hello";
e << 'T';
return 0;
}
The problem does not seem to be in the code snippet you posted. This program works fine:
#include "stdafx.h"
#include <string>
#include <iostream>
#include <sstream>
#include <windows.h>
class CAudit {
std::ostringstream audittext;
public:
std::string getAuditText() const { return audittext.str(); }
template<typename T>
CAudit& operator << ( const T& data ) {
audittext << data;
return *this;
}
CAudit& operator << ( int data ) {
audittext << data;
return *this;
}
CAudit& operator << ( LPCSTR data ) {
audittext << data;
return *this;
}
};
int main(int argc, char* argv[])
{
CAudit a;
int i = 123;
const char * s = "abc";
a << i;
a << s;
std::cout << "audittext is: '" << a.getAuditText() << "'\n";
return 0;
}
Could you post some more code?