Используйте функцию Stream-like Input to C++

Я знаю, что можно использовать макросы препроцессора для создания функции, которая принимает "потоковые" входные данные. Чтобы пояснить, что я имею в виду под «потоковым» вводом, позвольте мне привести пример (ввод в LOGGER является «потоковым»):

#include <iostream>
#include <sstream>

#define LOGGER(streamText) {   \
    std::ostringstream buffer; \
    buffer << streamText;      \
    /* Some processing */      \
    std::cout << buffer.str() << std::endl; \
}

int main(){
    LOGGER("Foo: " << "Bar!: " << 42);
}

Это выводит:

Фу: Бар!: 42

Я хотел бы иметь аналогичный интерфейс для обычной функции C++. Примерно так (не компилируется, недопустимый код C++):

// has:
//    - insert( std::ostringstream & str ) function for obtaining content of a std::ostringstream
//    - formatting functions
//    - print( std::ostream & str ) function
class logger_class;

logger_class logger_function( some_magic_type varName ){
    std::ostringstream & str = varName;
    logger_class log;
    log.insert( str );
    return log;
}

int main(){
    logger_class log = logger_function("Foo: " << "Bar!: " << 42);
    log.format( some_formatting_options );
    log.print( std::cout );
}

Это позволит отформатировать вывод перед его фактической отправкой в ​​std::cout.

Редактировать. Также разрешены решения C++11 и Boost.


person elemakil    schedule 15.01.2014    source источник
comment
Вы ищете перегруженные версии ‹‹-оператора (замена std::out или std::err с именем logger?)   -  person urzeit    schedule 15.01.2014
comment
@urzeit Вроде того. Я хочу убедиться, что регистратор может предположить, что ввод, который он только что передал, является полным сообщением. Позвольте мне привести вам пример: один из вариантов форматирования — это раскрашивание, которое (при использовании терминала ANSI) заключается в заключении содержимого в два разделителя. Используя макрос, это можно легко сделать, добавив разделители до и после потоковой передачи сообщения журнала в выходной поток (поскольку все сообщение журнала содержится в аргументе макроса).   -  person elemakil    schedule 15.01.2014
comment
Я хочу, чтобы функция возвращала объект регистратора, потому что это позволяет установить параметры форматирования с помощью цепочки методов, то есть: logger_function("Foo" << 42).log_level( 500 ).color( RED ).weight( BOLD ).end_line();   -  person elemakil    schedule 15.01.2014
comment
Кажется, вы смешиваете два стиля: цепочку методов и потоковую передачу (которая обычно использует манипуляторы вместо вызовов методов). Например, будет ли работать такой синтаксис: log() << "Foo" << 42 << log_level(500) << log_color(RED) << log_weight(BOLD) << endl; ?   -  person Useless    schedule 15.01.2014
comment
Да, я смешиваю потоковую передачу и цепочку методов. Я знаю. Потоковая передача выполняется для формирования аргумента функции, которая возвращает объект (содержащий данные, объединенные с помощью потоковой передачи). Этот объект можно настроить с помощью цепочки методов, и в конечном итоге он будет записывать сохраненные (и теперь отформатированные) данные в фактический исходящий поток.   -  person elemakil    schedule 15.01.2014
comment
Чтобы добиться раскрашивания с помощью потоковой передачи, достаточно написать: log() << log_level( 500 ) << log_color(RED) << log_weight(BOLD) << "Foo" << 42 << finish_color() << finish_weight() << std::endl;, потому что для раскрашивания ANSI требуется последовательность начало раскрашивания и конец раскрашивания.   -  person elemakil    schedule 15.01.2014


Ответы (1)


Используя С++ 11, вы можете использовать вариативные шаблоны:

template <typename T>
std::ostringstream & fill_stream(std::ostringstream & str, T&& t)
{
    return (str << std::forward<T>(t));
}

template <typename T, typename... Vs>
std::ostringstream & fill_stream(std::ostringstream & str, T&& t, Vs&&... vs)
{
    str << std::forward<T>(t);
    return fill_stream(std::forward<Vs>(vs)...);
}

template <typename... Ts>
logger_class logger_function( Ts&&... vars ){
    std::ostringstream & str;

    if (!fill_stream(str, std::forward<Ts>(vars)...))
    {
        //Error!
    }

    logger_class log;
    log.insert( str );
    return log;
}

Затем:

int main()
{
    logger_class log = logger_function("Foo: ", "Bar!: ", 42);
    log.format( some_formatting_options );
    log.print( std::cout );
}

Если вы хотите избежать немедленного создания потока строк, вы можете создать шаблонный класс, который хранит свои аргументы в кортеже.

template <typename... Ts>
logger_class<Ts&&...> logger_function( Ts&&... vars ){
    // logger_class should probably store its arguments as a tuple
    logger_class<Ts&&...> log ( std::forward<Ts>(vars)... );
    return log;
}

int main()
{
    auto log = logger_function("Foo: ", "Bar!: ", 42);
    log.format( some_formatting_options );
    log.print( std::cout );
}
person Apples    schedule 15.01.2014