При реализации C ++ 1z std::basic_string_view
использовать его на старых компиляторах, я столкнулся с проблемой перегрузка оператора потока для этого.
По сути, он должен выводить содержимое, на которое ссылается string_view
не полагаясь на присутствие нулевого терминатора (как string_view
не гарантируется прекращение
Обычно написание перегрузок для operator<<
это довольно просто, так как вы можете полагаться на уже существующие перегрузки, и, следовательно, не нужно использовать сторожевые объекты как уже упоминалось в этом вопросе о SO.
Но в этом случае нет предопределенной перегрузки для operator<<
взять указатель символа и длину (очевидно). Из-за этого я создаю временный std::string
экземпляр в моей текущей реализации:
template< typename TChar, typename TTraits >
auto operator<<(::std::basic_ostream<TChar, TTraits>& p_os, basic_string_view<TChar, TTraits> p_v)
-> ::std::basic_ostream<TChar, TTraits>&
{
p_os << p_v.to_string(); // to_string() returns a ::std::string.
return p_os;
}
Это работает, но я действительно не нравится тот факт, что я должен создать временный std::string
экземпляр, потому что это влечет за собой избыточное копирование данных и потенциал использование динамической памяти. Это, по моему мнению, по крайней мере, отрицательно сказывается на цели использования облегченного ссылочного типа.
Итак, мой вопрос:
Каков наилучший способ реализовать правильный форматированный вывод для моего string_view без издержек?
Во время исследования я обнаружил, что LLVM делает это так: (найдено Вот)
// [string.view.io]
template<class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, basic_string_view<_CharT, _Traits> __sv)
{
return _VSTD::__put_character_sequence(__os, __sv.data(), __sv.size());
}
Реализация __put_character_sequence
проживает в этом файле, но он интенсивно использует внутренние функции сделать форматирование. Нужно ли переопределять все форматирование самостоятельно?
Насколько я понимаю, вам придется справиться с этим самостоятельно.
К счастью, форматирование, которое вам нужно сделать для строкового элемента, довольно минимально — в большинстве случаев вставка отступа до или после строки, если это необходимо.
ios_base::width()
, ios_base::fmtflags()
, ios_base::fill()
,fixed
флаг — если память служит, с ней установлено, вам нужно обрезать строку, если она длиннее текущей ширины поля.Так (с ультра-упрощенной реализацией string_view
) код может выглядеть примерно так:
#include <iostream>
#include <iomanip>
#include <ios>
#include <sstream>
class string_view {
char const *data;
size_t len;
public:
string_view(char const *data, size_t len) : data(data), len(len) {}
friend std::ostream &operator<<(std::ostream &os, string_view const &sv) {
std::ostream::sentry s{ os };
if (s) {
auto fill = os.fill();
auto width = os.width();
bool left = os.flags() & std::ios::left;
bool right = os.flags() & std::ios::right;
bool fixed = os.flags() & std::ios::fixed;
auto pad = [&](size_t width) { while (width--) os.put(fill); };
if (sv.len < width) {
auto padding_len = width - sv.len;
if (right) pad(padding_len);
os.write(sv.data, sv.len);
if (left) pad(padding_len);
}
else {
os.write(sv.data, fixed ? width : sv.len);
}
}
os.width(0);
return os;
}
};
#ifdef TEST
void check(std::stringstream &a, std::stringstream &b) {
static int i;
++i;
if (a.str() != b.str()) {
std::cout << "Difference in test:" << i << "\n";
std::cout << "\"" << a.str() << "\"\n";
std::cout << "\"" << b.str() << "\"\n";
}
a.seekp(0);
b.seekp(0);
}
int main() {
char string[] = "Now is the time for every good man to come to the aid of Jerry.";
std::stringstream test1;
std::stringstream test2;
test1 << string_view(string, 3);
test2 << std::string(string, 3);
check(test1, test2);
test1 << string_view(string + 4, 2);
test2 << string_view(string + 4, 2);
check(test1, test2);
test1 << std::setw(10) << std::left << string_view(string, 6);
test2 << std::setw(10) << std::left << std::string(string, 6);
check(test1, test2);
test1 << std::setw(10) << std::right << string_view(string, 6);
test2 << std::setw(10) << std::right << std::string(string, 6);
check(test1, test2);
test1 << std::setw(10) << std::right << string_view(string, sizeof(string));
test2 << std::setw(10) << std::right << std::string(string, sizeof(string));
check(test1, test2);
test1 << std::setw(10) << std::right << std::fixed << string_view(string, sizeof(string));
test2 << std::setw(10) << std::right << std::fixed << std::string(string, sizeof(string));
check(test1, test2);
}
#endif
Ох — еще одна деталь. Так как мы пишем только в поток, а не напрямую в основной буфер, я считать нам, вероятно, на самом деле не нужно создавать sentry
объект в этом случае. Как показано, его создание и использование довольно тривиально, но с удалением это, несомненно, будет хоть немного быстрее.
Других решений пока нет …