I want to implement a streaming style logging lib.
I made a Log_t
class to buffer a log entry, and do real output up on it being
destroyed, like this:
class Log_t: public std::ostringstream {
static int log_id;
public:
Log_t() { ++log_id; };
~Log_t() override {
// in order to simplify discussion, here output it to cout
std::cout << "log" << log_id << ':' << str() << std::endl;
};
};
int Log_t::log_id {};
template <typename T>
Log_t& operator<<( Log_t& log_, const T& body_ ) {
static_cast<std::ostream&>( log_ ) << body_;
return log_;
};
Whenever I have a customized class/struct, I can log it in same way, as long as I implemented a output function for it. Like this:
struct Response_t {
int _id;
};
// Using std::ostream instead of Log_t, as it may be outputed to other targets
std::ostream& operator<<( std::ostream& os_, const Response_t& rsp_ ) {
return os_ << "resp_id:" << rsp_._id;
};
Then I can log it as:
Response_t response {123};
Log_t() << "local obj=" << response;
But in fact Response_t mainly is returned by a lib, and in pointer form instead of a real ojb/ref, so I should probably output the contents that be pointed by instead of a address, and I have to check if it is a nullptr, before logging it. Furthermore, I don't wana check if it is a nullptr in every customized output functions, instead I want to check it only once in the logging function template. I changed the function template into these two:
template <typename T>
Log_t& operator<<( Log_t& log_, const T* body_ ) {
if( body_ == nullptr )
static_cast<std::ostream&>( log_ ) << "{nullptr}";
else
static_cast<std::ostream&>( log_ ) << *body_;
return log_;
};
template <typename T>
Log_t& operator<<( Log_t& log_, const T& body_ ) {
static_assert( ! std::is_null_pointer_v<T> );
static_assert( ! std::is_pointer_v<T> );
static_cast<std::ostream&>( log_ ) << body_;
return log_;
};
The complete codes:
#include <iomanip>
#include <iostream>
#include <sstream>
#include <type_traits>
class Log_t: public std::ostringstream {
static int log_id;
public:
Log_t() { ++log_id; };
~Log_t() override { std::cout << "log" << log_id << ':' << str() << std::endl; };
};
int Log_t::log_id {};
/*
Let's say that parameter body_ is a pointer received from another lib,
so it may be a nullptr, and I don't wana check if it is a nullptr
everywhere in my projects, instead I want to check it only once in the
following function.
*/
template <typename T>
Log_t& operator<<( Log_t& log_, const T* body_ ) {
if( body_ == nullptr )
static_cast<std::ostream&>( log_ ) << "{nullptr}";
else
static_cast<std::ostream&>( log_ ) << *body_;
return log_;
};
template <typename T>
Log_t& operator<<( Log_t& log_, const T& body_ ) {
static_assert( ! std::is_null_pointer_v<T> );
static_assert( ! std::is_pointer_v<T> );
static_cast<std::ostream&>( log_ ) << body_;
return log_;
};
struct Response_t {
int _id;
};
std::ostream& operator<<( std::ostream& os_, const Response_t& rsp_ ) {
return os_ << "resp_id:" << rsp_._id;
};
int main() {
Response_t response {123};
Log_t() << "local obj=" << response;
// Suppose rsp_ptr is received from elsewhere, may be nullptr!
Response_t* rsp_ptr = &response;
Log_t() << "ptr obj=" << rsp_ptr;
rsp_ptr = nullptr;
Log_t() << "ptr obj=" << response;
exit( EXIT_SUCCESS );
};
But I can NOT pass through the compilation, and got this:
ostream-ptr.cpp:36:31: error: static assertion failed 36 |
static_assert( ! std::is_pointer_v );
It looks like the specialized version for pointers has been ignored by gcc-12.3?
Maybe I can resolve it with concept
of c++20?
How to code it?
The const T*
overload isn't getting ignored. The const T&
overload is just more specialized.
Consider this example:
template <typename T>
void f(const T*) {}
template <typename T>
void f(const T&) {}
int main() {
int i;
f(i); // calls the ref overload as expected, with T = int
int* p = &i;
f(p); // still calls the ref overload, but with T = int*
const int* pc = &i;
f(pc); // correctly calls the pointer overload, with T = int
}
If you add a third overload void f(T*)
without the const
, then the second call (f(p)
) uses that one.
I assume you want the const T*
overload to be called whenever a pointer is passed, const
or not. In this case, in C++20, you can add a requires
clause to disable the const T&
overload:
template <typename T>
requires(!std::is_pointer_v<T>)
Log_t& operator<<( Log_t& log_, const T& body_ ) { ... }
Before C++20, you can similarly use SFINAE.
Also, note that the line rsp_ptr = nullptr;
does not make rsp_ptr
a nullptr
in the sense that the type of rsp_ptr
does not change to std::nullptr_t
, which is what std::is_null_pointer
checks. It only sets rsp_ptr
to an integral value of zero.
To test your log for actual std::nullptr_t
values, you need to pass nullptr
explicitly (or make a std::nullptr_t
variable):
Log_t() << "ptr obj=" << nullptr;
This will fail, and so you may want to update the requires clause to:
requires(!std::is_pointer_v<T> and !std::is_null_pointer_v<T>)
You can also remove the static_assert
s, since they just repeat the requires clause.