Специализированный шаблон функции для всех типов указателей

Я хочу реализовать библиотеку ведения журнала в стиле потоковой передачи.

Я создал класс 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*&. Используйте SFINAE с std::is_pointer_v<std::remove_cvref_t<t>>

김선달 24.05.2024 10:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Перегрузка 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++ (!)

catnip 24.05.2024 12:32

@Nelfeal Большое спасибо! Но мы забыли кое-что. Как насчет «const char*»? Хоть это и указатель, но нам нужно вызвать с его помощью ref-версию функции. Должны ли мы?

Leon 24.05.2024 16:54

@Leon Стоит ли тебе это делать, это полностью твой выбор. Если вы имеете в виду, что всегда хотите рассматривать const char* как строку с нулевым завершением и передавать ее непосредственно в поток, то я бы просто добавил дополнительную перегрузку (не шаблонную и не имеющую приоритетов).

Nelfeal 24.05.2024 18:30

Другие вопросы по теме