Я хочу реализовать библиотеку ведения журнала в стиле потоковой передачи.
Я создал класс Log_t
для буферизации записи журнала и реального вывода по ней.
уничтожено вот так:
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_;
};
Всякий раз, когда у меня есть настроенный класс/структура, я могу зарегистрировать его таким же образом, если Я реализовал для него функцию вывода. Так:
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;
};
Тогда я могу зарегистрировать это как:
Response_t response {123};
Log_t() << "local obj = " << response;
Но на самом деле Response_t в основном возвращается библиотекой, а не в форме указателя. настоящего ojb/ref, поэтому мне, вероятно, следует вывести содержимое, на которое указывает вместо адреса, и мне нужно проверить, является ли это nullptr, прежде чем регистрировать его. Более того, я не хочу проверять, является ли это nullptr в каждом индивидуальном выводе. функции, вместо этого я хочу проверить это только один раз в шаблоне функции регистрации. Я изменил шаблон функции на эти два:
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_;
};
Полные коды:
#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 );
};
Но я НЕ могу пройти компиляцию и получил это:
ostream-ptr.cpp:36:31: ошибка: статическое утверждение не выполнено 36 |
static_assert(! std::is_pointer_v);
Похоже, gcc-12.3 проигнорировал специализированную версию для указателей?
Может быть, я смогу решить эту проблему с помощью C++ 20? Как это закодировать?
Перегрузка const T*
не игнорируется. Перегрузка const T&
просто более специализирована.
Рассмотрим этот пример:
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
}
Если вы добавите третью перегрузку void f(T*)
без const
, то второй вызов (f(p)
) будет использовать ее.
Я предполагаю, что вы хотите, чтобы перегрузка const T*
вызывалась всякий раз, когда передается указатель, const
или нет. В этом случае в C++20 вы можете добавить предложение requires
, чтобы отключить перегрузку const T&
:
template <typename T>
requires(!std::is_pointer_v<T>)
Log_t& operator<<( Log_t& log_, const T& body_ ) { ... }
До C++20 вы также можете использовать SFINAE.
Также обратите внимание, что строка rsp_ptr = nullptr;
не превращает rsp_ptr
в nullptr
в том смысле, что тип rsp_ptr
не меняется на std::nullptr_t
, что и проверяет std::is_null_pointer
. Он только устанавливает rsp_ptr
в целое значение, равное нулю.
Чтобы проверить журнал на наличие фактических значений std::nullptr_t
, вам необходимо явно передать nullptr
(или создать переменную std::nullptr_t
):
Log_t() << "ptr obj = " << nullptr;
Это не удастся, поэтому вы можете обновить предложение require следующим образом:
requires(!std::is_pointer_v<T> and !std::is_null_pointer_v<T>)
Вы также можете удалить static_assert
, поскольку они просто повторяют пункт require.
Хороший ответ. е(р); // по-прежнему вызывает перегрузку ref, но с T = int* — разве вы не любите C++ (!)
@Nelfeal Большое спасибо! Но мы забыли кое-что. Как насчет «const char*»? Хоть это и указатель, но нам нужно вызвать с его помощью ref-версию функции. Должны ли мы?
@Leon Стоит ли тебе это делать, это полностью твой выбор. Если вы имеете в виду, что всегда хотите рассматривать const char*
как строку с нулевым завершением и передавать ее непосредственно в поток, то я бы просто добавил дополнительную перегрузку (не шаблонную и не имеющую приоритетов).
Также возможно следующее:
const T*&
. Используйте SFINAE сstd::is_pointer_v<std::remove_cvref_t<t>>