Есть ли способ опустить пустые строковые литералы (""
) в списке аргументов функции fmt::format
?
У меня есть приведенный ниже фрагмент, который дает желаемый результат:
#include <string>
#include <fmt/core.h>
int main( )
{
const std::string application_layer_text_head { fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n", "", "" ) };
fmt::print( "{}", application_layer_text_head );
}
Выход:
*****[Application Layer]***************************************************
Итак, вместо того, чтобы писать это: fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n", "", "" )
, можем ли мы удалить пустые литералы и написать это: fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n" )
? Я пробовал, но не удалось скомпилировать. Эти два литерала на самом деле не служат никакой цели, поэтому я хочу найти способ не писать их.
Просто чтобы уточнить, я хочу иметь только 5 *
в начале, затем [Application Layer]
, затем 51 *
, а затем 2 \n
.
Это более эффективно, чем operator+
, но operator+=
и append
позволяют избежать большинства этих затрат (перераспределение на месте, а не создание новых string
и отбрасывание старых, и reserve
можно использовать для обеспечения единого распределения, если вам это действительно нужно).
Я думаю, что вопрос здесь действительно заключается в том, «Как мне отформатировать эту строку с 5 *
слева и 51 *
справа?», А не «как опустить пустые аргументы?» Или даже в более общем плане: как лучше всего отформатировать символ c
n
раз?
Разметка форматирования предназначена для форматирования строки с некоторой частью данных, предоставленных пользователем. Особенности специализированного синтаксиса в рамках форматирования могут регулировать работу этого форматирования, даже вставку символов и тому подобное. Но эта функциональность должна быть дополнением к основному действию: взять какой-либо предоставленный пользователем объект и внедрить его в строку.
Так что нет, у format
нет механизма, который позволил бы вам избежать предоставления данных, предоставленных пользователями, которые и являются основной причиной существования format
.
Также следует отметить, что само значение текста после :
в спецификаторе формата определяется исходя из типа форматируемого объекта. «*<5» означает «выровнять по 5 символам, используя символы «*» для заполнения пробелов» только потому, что вы предоставили строку для этого конкретного параметра формата. Так что format
не только не дает возможности сделать это, но и функционально не может. Вы должны указать, какой тип используется, потому что это неотъемлемая часть обработки значения "*<5".
Так есть ли другой лучший (более чистый) способ сделать это? Я выбрал формат, потому что он эффективен.
@digito_evo, вы можете создать собственный тип и написать для него собственные правила форматирования.
Как уже отмечалось, format
не может этого сделать. Но ваши опасения по поводу дороговизны конкатенации строк неуместны; повторное применение operator+
дорого (выполняет новые распределения, копирует все существующие данные и новые данные, отбрасывает старые данные снова и снова), но конкатенация на месте с operator+=
и append
дешева, особенно если вы предварительно reserve
(так что вы перераспределение сразу и заполнение, не полагаясь на модели амортизированного роста при перераспределении, чтобы спасти вас). Даже без предварительной reserve
std::string
следует амортизированным моделям роста, поэтому повторная конкатенация на месте амортизируется O(1)
(на каждый добавленный символ), а не O(n)
в размере данных до сих пор.
Следующее должно быть, по сути, по определению, по крайней мере таким же быстрым, как форматирование, хотя и за счет большего количества строк кода для выполнения предварительной reserve
для предотвращения перераспределения:
#include <string>
#include <string_view>
#include <fmt/core.h>
using namespace std::literals;
int main( )
{
// Make empty string, and reserve enough space for final form
auto application_layer_text_head = std::string();
application_layer_text_head.reserve(5 + "[Application Layer]"sv.size() + 51 + "\n\n"sv.size());
// append is in-place, returning reference to original string, so it can be chained
// Using string_view literals to avoid need for any temporary runtime allocated strings,
// while still allowing append to use known length concatenation to save scanning for NUL
application_layer_text_head.append(5, '*').append("[Application Layer]"sv).append(51, '*').append("\n\n"sv);
fmt::print("{}", application_layer_text_head);
}
Если вас устраивали некоторые конкатенации, потенциально выполняющие перераспределение, и конструкция окончательного перемещения для перемещения ресурсов из временной ссылки в реальную строку, это упрощается до однострочного:
const auto application_layer_text_head = std::move(std::string(5, '*').append("[Application Layer]"sv).append(51, '*').append("\n\n"sv));
или, учитывая, что 5 звездочек достаточно для ввода, еще более короткая/простая версия:
const auto application_layer_text_head = std::move("*****[Application Layer]"s.append(51, '*').append("\n\n"sv));
Но сохранение его двухстрочным позволяет избежать конструкции перемещения и немного безопаснее:
auto application_layer_text_head = "*****[Application Layer]"s;
application_layer_text_head.append(51, '*').append("\n\n"sv);
Да, ни один из них не так красив, как литерал одного формата, даже с «уродливыми» пустыми заполнителями. Если вы предпочитаете внешний вид строки формата, просто передайте пустые заполнители так, как вы это уже делаете.
Ваш один вкладыш создает строку, а затем копирует ее, плюс он довольно открыт для зависания (если вместо этого вы написали auto const&
или auto&&
).
@Barry: Хм ... Вероятно, вы правы, поскольку он возвращается по ссылке, а не по значению; Я предполагаю, что std::move
можно использовать, по крайней мере, для того, чтобы сделать его конструкцией перемещения, и хотя я не могу представить, почему вы объявляете его ссылкой напрямую, это будет проблемой, если вы попытаетесь встроить его в вызов метода, который его получил. по (висящей) ссылке. Так что да, наверное, лучше всего в две строки.
Спасибо за предложение. И если говорить о висячих ссылках, это действительно происходит в const auto application_layer_text_head = std::string(5, '*').append("[Application Layer]"sv).
? Разве он не собирается делать конструкцию перемещения, потому что объект с правой стороны является временным (rvalue)?
@digito_evo Правая часть - это не rvalue, это lvalue.
@Барри Но почему? Это значение без какого-либо идентификатора. Как это может быть lvalue?
@digito_evo Потому что идентификаторы - это не единственные вещи, которые являются lvalue. string::append()
возвращает ссылку lvalue.
@digito_evo: Причина, по которой он ведет себя таким образом, заключается в том, что он изменяет str на месте, чтобы избежать копирования, а возвращаемое значение всегда является ссылкой l-значения на исходный str. Это позволяет связывать вызовы методов без накладных расходов и не требует переназначения, когда у объекта уже есть имя, но это означает, что даже когда вы изначально работаете с r-значением, он все равно возвращает ссылку на l-значение. Они могли бы добавить версии append
с указанием r-значения, но они не сделали этого с тех пор, как эта функция была добавлена в C++ 11. Такова жизнь.
@ Никол Болас Я хочу не только 5
*
. Посмотрите на выводимый текст. Это то, что я хочу. Иfmt::format
хорошо работает в этом случае и намного эффективнее, чемoperator+
изstd::string
. Так что есть смысл его использовать.