Используя gcc 4.8 с включенным C ++ 11, у меня есть такой класс:
class OutStream {
public:
OutStream& operator<<(const char* s);
OutStream& operator<<(int n);
OutStream& operator<<(unsigned int n);
// ...
OutStream& vformat(const char* fmt, __VALIST args);
OutStream& format(const char* fmt, ...);
};
Когда я использую этот класс, вызывая операторы напрямую, он работает как я ожидал:
OutStream out;
out.operator<<(1).format(" formatted %04X ", 2).operator<<("3\n");
Выход:
1 formatted 0002 3
Теперь я хотел бы получить тот же результат, но с помощью <<
потоковая запись, может быть, так:
OutStream out;
out << 1 << format(" formatted %04X ", 2) << "3\n";
Конечно, это не скомпилируется, потому что не было такого оператора для потоковой передачи OutStream.format()
метод.
Там может быть решение, где format()
была свободная функция, которая возвращает строку, но для этого нужно сначала записать все выходные данные format()
в буфер. Мне нужно решение без std::string
или другое использование кучи или буфера — в лучшем случае решение, которое создает почти такой же код, как и при непосредственном вызове операторов.
Какие-либо предложения?
Изменить, 2014-10-20:
Stl
iostream
, Тем не менее iostream
тег был установлен seh edit; Я бы оставил его установленным из-за тематического соответствия, и найденное решение для моей проблемы также может быть применимо для Stl
iostream
,Использование C ++ 14 index_sequence (Есть миллион различных реализаций на SO):
template <typename...Ts>
class formatter {
const char* fmt_;
std::tuple<Ts...> args_;
template <std::size_t...Is>
void expand(OutStream& os, std::index_sequence<Is...>) && {
os.format(fmt_, std::get<Is>(std::move(args_))...);
}
public:
template <typename...Args>
formatter(const char* fmt, Args&&...args) :
fmt_{fmt}, args_{std::forward<Args>(args)...} {}
friend OutStream& operator << (OutStream& os, formatter&& f) {
std::move(f).expand(os, std::index_sequence_for<Ts...>{});
return os;
}
};
template <typename...Args>
formatter<Args&&...> format(const char* fmt, Args&&...args) {
return {fmt, std::forward<Args>(args)...};
}
Компилятор должен легко иметь возможность встроить операцию formatter
и исключить временный объект. Действительно эта функция:
void test_foo() {
OutStream out;
out << 1 << format(" formatted %04X ", 2) << "3\n";
}
результаты в сборке (g ++ 4.9.0 -std = c ++ 1y -O3 для x64):
.LC0:
.string " formatted %04X ".LC1:
.string "3\n"test_foo():
pushq %rbx
movl $1, %esi
subq $16, %rsp
leaq 15(%rsp), %rdi
call OutStream::operator<<(int)
movl $2, %edx
movl $.LC0, %esi
movq %rax, %rbx
movq %rax, %rdi
xorl %eax, %eax
call OutStream::format(char const*, ...)
movq %rbx, %rdi
movl $.LC1, %esi
call OutStream::operator<<(char const*)
addq $16, %rsp
popq %rbx
ret
так что все правильно встроено; нет никаких следов formatter
в произведенном коде.
Есть три точки расширения для класса std::basic_ostream
И его operator<<
что выглядит актуально здесь:
std::ios_base&
,std::basic_ios<C, T>&
,std::basic_ostream&
,К сожалению, все три работают с указателями на функции, а не std::function
случаи, что затрудняет поставку закрытия. В вашем случае вы хотели бы предоставить строку формата — и, возможно, аргументы формата — а-ля std::setw()
.
Вы можете найти обсуждение того, как реализовать эти манипуляторы, в старом эссе Кей Хорстманн. Расширение библиотеки iostream. В частности, посмотрите в разделе 3 «Манипуляторы», чтобы увидеть, как вы можете вернуть объект из вашего format()
функция, которая служит закрытием, и написать operator<<()
функция для этого объекта.
Это потребует некоторого дополнительного копирования, если вы хотите захватить временные значения в вашем замыкании, и у вас могут возникнуть трудности с захватом списка переменных аргументов. Начните с простого интерфейса (возможно, только с одним аргументом), убедитесь, что он пишет в целевой поток, и соберите его оттуда.
Попробуйте следующее:
OutStream out;
(out << 1).format(" formatted %04X ", 2) << "3\n";
Рассмотреть возможность использования GNU’s autosprintf. Это очень мало. Нет, действительно. По сути, это обертка вокруг vasprintf
, Все, что нужно autosprintf — это std::string
реализация и ваши обычные автономные заголовки C. Здесь заголовочный файл а также документация. Пример того, как вы можете использовать это:
OutStream out;
out << 1 << gnu::autosprintf(" formatted %04X ", 2) << "3\n";
(На самом деле, если вы используете строки фиксированного размера, вы можете изменить это, чтобы избежать любого использования std::string
совсем. Конечно, есть еще предположение, что вы реализовали vasprintf
и некоторая форма выделения кучи.)